Coverage for tests/test_translation.py: 12%

123 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-03-20 11:09 +0000

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. 

11 

12import os.path 

13import unittest 

14 

15from astropy.time import Time, TimeDelta 

16 

17from astro_metadata_translator import FitsTranslator, ObservationInfo, StubTranslator 

18 

19TESTDIR = os.path.abspath(os.path.dirname(__file__)) 

20 

21 

22class InstrumentTestTranslator(FitsTranslator, StubTranslator): 

23 """Simple FITS-like translator to test the infrastructure.""" 

24 

25 # Needs a name to be registered 

26 name = "TestTranslator" 

27 

28 # Indicate the instrument this class understands 

29 supported_instrument = "SCUBA_test" 

30 

31 # Some new mappings, including an override 

32 _trivial_map = { 

33 "telescope": "TELCODE", 

34 "exposure_id": "EXPID", 

35 "relative_humidity": "HUMIDITY", 

36 "detector_name": "DETNAME", 

37 "observation_id": "OBSID", 

38 } 

39 

40 # Add translator method to test joining 

41 def to_physical_filter(self): 

42 return self._join_keyword_values(["DETNAME", "HUMIDITY"], delim="_") 

43 

44 

45class MissingMethodsTranslator(FitsTranslator): 

46 """Translator class that does not implement all the methods.""" 

47 

48 pass 

49 

50 

51class TranslatorTestCase(unittest.TestCase): 

52 """Test core translation infrastructure.""" 

53 

54 def setUp(self): 

55 # Known simple header 

56 self.header = { 

57 "TELESCOP": "JCMT", 

58 "TELCODE": "LSST", 

59 "INSTRUME": "SCUBA_test", 

60 "DATE-OBS": "2000-01-01T01:00:01.500", 

61 "DATE-END": "2000-01-01T02:00:01.500", 

62 "OBSGEO-X": "-5464588.84421314", 

63 "OBSGEO-Y": "-2493000.19137644", 

64 "OBSGEO-Z": "2150653.35350771", 

65 "OBSID": "20000101_00002", 

66 "EXPID": "22", # Should cast to a number 

67 "DETNAME": 76, # Should cast to a string 

68 "HUMIDITY": "55", # Should cast to a float 

69 "BAZ": "bar", 

70 } 

71 

72 def test_manual_translation(self): 

73 header = self.header 

74 translator = FitsTranslator(header) 

75 

76 # Treat the header as standard FITS 

77 self.assertFalse(FitsTranslator.can_translate(header)) 

78 self.assertEqual(translator.to_telescope(), "JCMT") 

79 self.assertEqual(translator.to_instrument(), "SCUBA_test") 

80 self.assertEqual(translator.to_datetime_begin(), Time(header["DATE-OBS"], format="isot")) 

81 

82 # This class will issue warnings 

83 with self.assertLogs("astro_metadata_translator") as cm: 

84 

85 class InstrumentTestTranslatorExtras(InstrumentTestTranslator): 

86 """Version of InstrumentTestTranslator with unexpected 

87 fields. 

88 """ 

89 

90 name = "InstrumentTestTranslatorExtras" 

91 _trivial_map = {"foobar": "BAZ"} 

92 _const_map = {"format": "HDF5"} 

93 

94 self.assertIn("Unexpected trivial", cm.output[0]) 

95 self.assertIn("Unexpected constant", cm.output[1]) 

96 

97 # Use the special test translator instead 

98 translator = InstrumentTestTranslatorExtras(header) 

99 self.assertTrue(InstrumentTestTranslator.can_translate(header)) 

100 self.assertEqual(translator.to_telescope(), "LSST") 

101 self.assertEqual(translator.to_instrument(), "SCUBA_test") 

102 self.assertEqual(translator.to_format(), "HDF5") 

103 self.assertEqual(translator.to_foobar(), "bar") 

104 

105 def test_translator(self): 

106 header = self.header 

107 

108 # Specify a translation class 

109 with self.assertWarns(UserWarning): 

110 # Since the translator is incomplete it should issue warnings 

111 v1 = ObservationInfo(header, translator_class=InstrumentTestTranslator) 

112 self.assertEqual(v1.instrument, "SCUBA_test") 

113 self.assertEqual(v1.telescope, "LSST") 

114 self.assertEqual(v1.exposure_id, 22) 

115 self.assertIsInstance(v1.exposure_id, int) 

116 self.assertEqual(v1.detector_name, "76") 

117 self.assertEqual(v1.relative_humidity, 55.0) 

118 self.assertIsInstance(v1.relative_humidity, float) 

119 self.assertEqual(v1.physical_filter, "76_55") 

120 

121 # Now automated class 

122 with self.assertWarns(UserWarning): 

123 # Since the translator is incomplete it should issue warnings 

124 v1 = ObservationInfo(header) 

125 self.assertEqual(v1.instrument, "SCUBA_test") 

126 self.assertEqual(v1.telescope, "LSST") 

127 

128 location = v1.location.to_geodetic() 

129 self.assertAlmostEqual(location.height.to("m").to_value(), 4123.0, places=1) 

130 

131 # Check that headers have been removed 

132 new_hdr = v1.stripped_header() 

133 self.assertNotIn("INSTRUME", new_hdr) 

134 self.assertNotIn("OBSGEO-X", new_hdr) 

135 self.assertIn("TELESCOP", new_hdr) 

136 

137 # Check the list of cards that were used 

138 used = v1.cards_used 

139 self.assertIn("INSTRUME", used) 

140 self.assertIn("OBSGEO-Y", used) 

141 self.assertNotIn("TELESCOP", used) 

142 

143 # Stringification 

144 summary = str(v1) 

145 self.assertIn("datetime_begin", summary) 

146 

147 # Create with a subset of properties 

148 v2 = ObservationInfo( 

149 header, 

150 translator_class=InstrumentTestTranslator, 

151 subset={"telescope", "datetime_begin", "exposure_group"}, 

152 ) 

153 

154 self.assertEqual(v2.telescope, v1.telescope) 

155 self.assertEqual(v2.datetime_begin, v2.datetime_begin) 

156 self.assertIsNone(v2.datetime_end) 

157 self.assertIsNone(v2.location) 

158 self.assertIsNone(v2.observation_id) 

159 

160 def test_corrections(self): 

161 """Apply corrections before translation.""" 

162 header = self.header 

163 

164 # Specify a translation class 

165 with self.assertWarns(UserWarning): 

166 # Since the translator is incomplete it should issue warnings 

167 v1 = ObservationInfo( 

168 header, 

169 translator_class=InstrumentTestTranslator, 

170 search_path=[os.path.join(TESTDIR, "data", "corrections")], 

171 ) 

172 

173 # These values should match the expected translation 

174 self.assertEqual(v1.instrument, "SCUBA_test") 

175 self.assertEqual(v1.detector_name, "76") 

176 self.assertEqual(v1.relative_humidity, 55.0) 

177 self.assertIsInstance(v1.relative_humidity, float) 

178 self.assertEqual(v1.physical_filter, "76_55") 

179 

180 # These two should be the "corrected" values 

181 self.assertEqual(v1.telescope, "AuxTel") 

182 self.assertEqual(v1.exposure_id, 42) 

183 

184 def test_failures(self): 

185 header = {} 

186 

187 with self.assertRaises(TypeError): 

188 ObservationInfo(header, translator_class=ObservationInfo) 

189 

190 with self.assertRaises(ValueError): 

191 ObservationInfo( 

192 header, translator_class=InstrumentTestTranslator, subset={"definitely_not_known"} 

193 ) 

194 

195 with self.assertRaises(ValueError): 

196 ObservationInfo( 

197 header, translator_class=InstrumentTestTranslator, required={"definitely_not_known"} 

198 ) 

199 

200 with self.assertLogs("astro_metadata_translator"): 

201 with self.assertWarns(UserWarning): 

202 ObservationInfo(header, translator_class=InstrumentTestTranslator, pedantic=False) 

203 

204 with self.assertRaises(KeyError): 

205 with self.assertWarns(UserWarning): 

206 ObservationInfo(header, translator_class=InstrumentTestTranslator, pedantic=True) 

207 

208 # Pass in header where the key does exist but the value is bad. 

209 bad_header = { 

210 "OBSGEO-X": "not-float", 

211 "OBSGEO-Y": "not-float", 

212 "OBSGEO-Z": "not-float", 

213 "TELESCOP": "JCMT", 

214 "TELCODE": "LSST", 

215 "INSTRUME": "SCUBA_test", 

216 } 

217 

218 with self.assertLogs("astro_metadata_translator"): 

219 with self.assertWarns(UserWarning): 

220 ObservationInfo(bad_header, translator_class=InstrumentTestTranslator, pedantic=False) 

221 

222 with self.assertLogs("astro_metadata_translator"): 

223 with self.assertWarns(UserWarning): 

224 ObservationInfo( 

225 header, translator_class=InstrumentTestTranslator, pedantic=False, filename="testfile1" 

226 ) 

227 

228 with self.assertRaises(KeyError): 

229 with self.assertWarns(UserWarning): 

230 ObservationInfo( 

231 header, translator_class=InstrumentTestTranslator, pedantic=True, filename="testfile2" 

232 ) 

233 

234 with self.assertRaises(NotImplementedError): 

235 with self.assertLogs("astro_metadata_translator", level="WARN"): 

236 ObservationInfo(header, translator_class=MissingMethodsTranslator) 

237 

238 with self.assertRaises(KeyError): 

239 with self.assertWarns(UserWarning): 

240 with self.assertLogs("astro_metadata_translator", level="WARN"): 

241 ObservationInfo( 

242 header, 

243 translator_class=InstrumentTestTranslator, 

244 pedantic=False, 

245 required={"boresight_airmass"}, 

246 ) 

247 

248 def test_observing_date(self): 

249 """Test the observing_date class methods.""" 

250 date1 = Time("2023-07-06T13:00", format="isot", scale="tai") 

251 day_obs = StubTranslator.observing_date_to_observing_day(date1, 12 * 3600) 

252 self.assertEqual(day_obs, 20230706) 

253 

254 date2 = Time("2023-07-07T11:00", format="isot", scale="tai") 

255 day_obs = StubTranslator.observing_date_to_observing_day(date2, TimeDelta("0.5d", scale="tai")) 

256 self.assertEqual(day_obs, 20230706) 

257 day_obs = StubTranslator.observing_date_to_observing_day(date2, 1.0) 

258 self.assertEqual(day_obs, 20230707) 

259 

260 

261if __name__ == "__main__": 261 ↛ 262line 261 didn't jump to line 262, because the condition on line 261 was never true

262 unittest.main()