Coverage for tests/test_translation.py: 12%
114 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-21 03:05 -0700
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-21 03:05 -0700
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.
12import os.path
13import unittest
15from astropy.time import Time
17from astro_metadata_translator import FitsTranslator, ObservationInfo, StubTranslator
19TESTDIR = os.path.abspath(os.path.dirname(__file__))
22class InstrumentTestTranslator(FitsTranslator, StubTranslator):
23 """Simple FITS-like translator to test the infrastructure"""
25 # Needs a name to be registered
26 name = "TestTranslator"
28 # Indicate the instrument this class understands
29 supported_instrument = "SCUBA_test"
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 }
40 # Add translator method to test joining
41 def to_physical_filter(self):
42 return self._join_keyword_values(["DETNAME", "HUMIDITY"], delim="_")
45class MissingMethodsTranslator(FitsTranslator):
46 """Translator class that does not implement all the methods."""
48 pass
51class TranslatorTestCase(unittest.TestCase):
52 def setUp(self):
53 # Known simple header
54 self.header = {
55 "TELESCOP": "JCMT",
56 "TELCODE": "LSST",
57 "INSTRUME": "SCUBA_test",
58 "DATE-OBS": "2000-01-01T01:00:01.500",
59 "DATE-END": "2000-01-01T02:00:01.500",
60 "OBSGEO-X": "-5464588.84421314",
61 "OBSGEO-Y": "-2493000.19137644",
62 "OBSGEO-Z": "2150653.35350771",
63 "OBSID": "20000101_00002",
64 "EXPID": "22", # Should cast to a number
65 "DETNAME": 76, # Should cast to a string
66 "HUMIDITY": "55", # Should cast to a float
67 "BAZ": "bar",
68 }
70 def test_manual_translation(self):
71 header = self.header
72 translator = FitsTranslator(header)
74 # Treat the header as standard FITS
75 self.assertFalse(FitsTranslator.can_translate(header))
76 self.assertEqual(translator.to_telescope(), "JCMT")
77 self.assertEqual(translator.to_instrument(), "SCUBA_test")
78 self.assertEqual(translator.to_datetime_begin(), Time(header["DATE-OBS"], format="isot"))
80 # This class will issue warnings
81 with self.assertLogs("astro_metadata_translator") as cm:
83 class InstrumentTestTranslatorExtras(InstrumentTestTranslator):
84 """Version of InstrumentTestTranslator with unexpected
85 fields."""
87 name = "InstrumentTestTranslatorExtras"
88 _trivial_map = {"foobar": "BAZ"}
89 _const_map = {"format": "HDF5"}
91 self.assertIn("Unexpected trivial", cm.output[0])
92 self.assertIn("Unexpected constant", cm.output[1])
94 # Use the special test translator instead
95 translator = InstrumentTestTranslatorExtras(header)
96 self.assertTrue(InstrumentTestTranslator.can_translate(header))
97 self.assertEqual(translator.to_telescope(), "LSST")
98 self.assertEqual(translator.to_instrument(), "SCUBA_test")
99 self.assertEqual(translator.to_format(), "HDF5")
100 self.assertEqual(translator.to_foobar(), "bar")
102 def test_translator(self):
103 header = self.header
105 # Specify a translation class
106 with self.assertWarns(UserWarning):
107 # Since the translator is incomplete it should issue warnings
108 v1 = ObservationInfo(header, translator_class=InstrumentTestTranslator)
109 self.assertEqual(v1.instrument, "SCUBA_test")
110 self.assertEqual(v1.telescope, "LSST")
111 self.assertEqual(v1.exposure_id, 22)
112 self.assertIsInstance(v1.exposure_id, int)
113 self.assertEqual(v1.detector_name, "76")
114 self.assertEqual(v1.relative_humidity, 55.0)
115 self.assertIsInstance(v1.relative_humidity, float)
116 self.assertEqual(v1.physical_filter, "76_55")
118 # Now automated class
119 with self.assertWarns(UserWarning):
120 # Since the translator is incomplete it should issue warnings
121 v1 = ObservationInfo(header)
122 self.assertEqual(v1.instrument, "SCUBA_test")
123 self.assertEqual(v1.telescope, "LSST")
125 location = v1.location.to_geodetic()
126 self.assertAlmostEqual(location.height.to("m").to_value(), 4123.0, places=1)
128 # Check that headers have been removed
129 new_hdr = v1.stripped_header()
130 self.assertNotIn("INSTRUME", new_hdr)
131 self.assertNotIn("OBSGEO-X", new_hdr)
132 self.assertIn("TELESCOP", new_hdr)
134 # Check the list of cards that were used
135 used = v1.cards_used
136 self.assertIn("INSTRUME", used)
137 self.assertIn("OBSGEO-Y", used)
138 self.assertNotIn("TELESCOP", used)
140 # Stringification
141 summary = str(v1)
142 self.assertIn("datetime_begin", summary)
144 # Create with a subset of properties
145 v2 = ObservationInfo(
146 header,
147 translator_class=InstrumentTestTranslator,
148 subset={"telescope", "datetime_begin", "exposure_group"},
149 )
151 self.assertEqual(v2.telescope, v1.telescope)
152 self.assertEqual(v2.datetime_begin, v2.datetime_begin)
153 self.assertIsNone(v2.datetime_end)
154 self.assertIsNone(v2.location)
155 self.assertIsNone(v2.observation_id)
157 def test_corrections(self):
158 """Apply corrections before translation."""
159 header = self.header
161 # Specify a translation class
162 with self.assertWarns(UserWarning):
163 # Since the translator is incomplete it should issue warnings
164 v1 = ObservationInfo(
165 header,
166 translator_class=InstrumentTestTranslator,
167 search_path=[os.path.join(TESTDIR, "data", "corrections")],
168 )
170 # These values should match the expected translation
171 self.assertEqual(v1.instrument, "SCUBA_test")
172 self.assertEqual(v1.detector_name, "76")
173 self.assertEqual(v1.relative_humidity, 55.0)
174 self.assertIsInstance(v1.relative_humidity, float)
175 self.assertEqual(v1.physical_filter, "76_55")
177 # These two should be the "corrected" values
178 self.assertEqual(v1.telescope, "AuxTel")
179 self.assertEqual(v1.exposure_id, 42)
181 def test_failures(self):
182 header = {}
184 with self.assertRaises(TypeError):
185 ObservationInfo(header, translator_class=ObservationInfo)
187 with self.assertRaises(ValueError):
188 ObservationInfo(
189 header, translator_class=InstrumentTestTranslator, subset={"definitely_not_known"}
190 )
192 with self.assertRaises(ValueError):
193 ObservationInfo(
194 header, translator_class=InstrumentTestTranslator, required={"definitely_not_known"}
195 )
197 with self.assertLogs("astro_metadata_translator"):
198 with self.assertWarns(UserWarning):
199 ObservationInfo(header, translator_class=InstrumentTestTranslator, pedantic=False)
201 with self.assertRaises(KeyError):
202 with self.assertWarns(UserWarning):
203 ObservationInfo(header, translator_class=InstrumentTestTranslator, pedantic=True)
205 # Pass in header where the key does exist but the value is bad.
206 bad_header = {
207 "OBSGEO-X": "not-float",
208 "OBSGEO-Y": "not-float",
209 "OBSGEO-Z": "not-float",
210 "TELESCOP": "JCMT",
211 "TELCODE": "LSST",
212 "INSTRUME": "SCUBA_test",
213 }
215 with self.assertLogs("astro_metadata_translator"):
216 with self.assertWarns(UserWarning):
217 ObservationInfo(bad_header, translator_class=InstrumentTestTranslator, pedantic=False)
219 with self.assertLogs("astro_metadata_translator"):
220 with self.assertWarns(UserWarning):
221 ObservationInfo(
222 header, translator_class=InstrumentTestTranslator, pedantic=False, filename="testfile1"
223 )
225 with self.assertRaises(KeyError):
226 with self.assertWarns(UserWarning):
227 ObservationInfo(
228 header, translator_class=InstrumentTestTranslator, pedantic=True, filename="testfile2"
229 )
231 with self.assertRaises(NotImplementedError):
232 with self.assertLogs("astro_metadata_translator", level="WARN"):
233 ObservationInfo(header, translator_class=MissingMethodsTranslator)
235 with self.assertRaises(KeyError):
236 with self.assertWarns(UserWarning):
237 with self.assertLogs("astro_metadata_translator", level="WARN"):
238 ObservationInfo(
239 header,
240 translator_class=InstrumentTestTranslator,
241 pedantic=False,
242 required={"boresight_airmass"},
243 )
246if __name__ == "__main__": 246 ↛ 247line 246 didn't jump to line 247, because the condition on line 246 was never true
247 unittest.main()