Coverage for tests/test_visitInfo.py : 13%

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#
2# LSST Data Management System
3# Copyright 2016 LSST Corporation.
4#
5# This product includes software developed by the
6# LSST Project (http://www.lsst.org/).
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the LSST License Statement and
19# the GNU General Public License along with this program. If not,
20# see <http://www.lsstcorp.org/LegalNotices/>.
21#
22import math
23import os
24import unittest
25import collections
26import numpy as np
28import lsst.utils.tests
29import lsst.pex.exceptions
30from lsst.daf.base import DateTime, PropertySet, PropertyList
31from lsst.geom import Angle, degrees, SpherePoint
32from lsst.afw.coord import Observatory, Weather
33import lsst.afw.image as afwImage
35RotTypeEnumNameDict = {
36 afwImage.RotType.UNKNOWN: "UNKNOWN",
37 afwImage.RotType.SKY: "SKY",
38 afwImage.RotType.HORIZON: "HORIZON",
39 afwImage.RotType.MOUNT: "MOUNT",
40}
43def propertySetFromDict(keyValDict):
44 """Make an lsst.daf.base.PropertySet from a dict of key: value"""
45 metadata = PropertySet()
46 for key, val in keyValDict.items():
47 metadata.set(key, val)
48 return metadata
51class VisitInfoTestCase(lsst.utils.tests.TestCase):
52 """Test lsst.afw.image.VisitInfo, a simple struct-like class"""
54 def setUp(self):
55 self.testDir = os.path.dirname(__file__)
57 def computeLstHA(data):
58 """Return LST, Hour Angle, computed from VisitInfoData."""
59 localEra = data.era + data.observatory.getLongitude()
60 hourAngle = localEra - data.boresightRaDec[0]
61 return localEra, hourAngle
63 fields = ['exposureId',
64 'exposureTime',
65 'darkTime',
66 'date',
67 'ut1',
68 'era',
69 'boresightRaDec',
70 'boresightAzAlt',
71 'boresightAirmass',
72 'boresightRotAngle',
73 'rotType',
74 'observatory',
75 'weather'
76 ]
77 VisitInfoData = collections.namedtuple("VisitInfoData", fields)
78 data1 = VisitInfoData(exposureId=10313423,
79 exposureTime=10.01,
80 darkTime=11.02,
81 date=DateTime(
82 65321.1, DateTime.MJD, DateTime.TAI),
83 ut1=12345.1,
84 era=45.1*degrees,
85 boresightRaDec=SpherePoint(
86 23.1*degrees, 73.2*degrees),
87 boresightAzAlt=SpherePoint(
88 134.5*degrees, 33.3*degrees),
89 boresightAirmass=1.73,
90 boresightRotAngle=73.2*degrees,
91 rotType=afwImage.RotType.SKY,
92 observatory=Observatory(
93 11.1*degrees, 22.2*degrees, 0.333),
94 weather=Weather(1.1, 2.2, 34.5),
95 )
96 self.data1 = data1
97 self.localEra1, self.hourAngle1 = computeLstHA(data1)
98 data2 = VisitInfoData(exposureId=1,
99 exposureTime=15.5,
100 darkTime=17.8,
101 date=DateTime(
102 55321.2, DateTime.MJD, DateTime.TAI),
103 ut1=312345.1,
104 era=25.1*degrees,
105 boresightRaDec=SpherePoint(
106 2.1*degrees, 33.2*degrees),
107 boresightAzAlt=SpherePoint(13.5*degrees, 83.3*degrees),
108 boresightAirmass=2.05,
109 boresightRotAngle=-53.2*degrees,
110 rotType=afwImage.RotType.HORIZON,
111 observatory=Observatory(
112 22.2*degrees, 33.3*degrees, 0.444),
113 weather=Weather(2.2, 3.3, 44.4),
114 )
115 self.data2 = data2
116 self.localEra2, self.hourAngle2 = computeLstHA(data2)
118 def _testValueConstructor(self, data, localEra, hourAngle):
119 visitInfo = afwImage.VisitInfo(data.exposureId,
120 data.exposureTime,
121 data.darkTime,
122 data.date,
123 data.ut1,
124 data.era,
125 data.boresightRaDec,
126 data.boresightAzAlt,
127 data.boresightAirmass,
128 data.boresightRotAngle,
129 data.rotType,
130 data.observatory,
131 data.weather,
132 )
133 self.assertEqual(visitInfo.getExposureId(), data.exposureId)
134 self.assertEqual(visitInfo.getExposureTime(), data.exposureTime)
135 self.assertEqual(visitInfo.getDarkTime(), data.darkTime)
136 self.assertEqual(visitInfo.getDate(), data.date)
137 self.assertEqual(visitInfo.getUt1(), data.ut1)
138 self.assertEqual(visitInfo.getEra(), data.era)
139 self.assertEqual(visitInfo.getBoresightRaDec(), data.boresightRaDec)
140 self.assertEqual(visitInfo.getBoresightAzAlt(), data.boresightAzAlt)
141 self.assertEqual(visitInfo.getBoresightAirmass(),
142 data.boresightAirmass)
143 self.assertEqual(visitInfo.getBoresightRotAngle(),
144 data.boresightRotAngle)
145 self.assertEqual(visitInfo.getRotType(), data.rotType)
146 self.assertEqual(visitInfo.getObservatory(), data.observatory)
147 self.assertEqual(visitInfo.getWeather(), data.weather)
148 self.assertEqual(visitInfo.getLocalEra(), localEra)
149 self.assertEqual(visitInfo.getBoresightHourAngle(), hourAngle)
151 def testValueConstructor_data1(self):
152 self._testValueConstructor(self.data1, self.localEra1, self.hourAngle1)
154 def testValueConstructor_data2(self):
155 self._testValueConstructor(self.data2, self.localEra2, self.hourAngle2)
157 def testTablePersistence(self):
158 for item in (self.data1, self.data2):
159 tablePath = os.path.join(
160 self.testDir, "testVisitInfo_testTablePersistence.fits")
161 v1 = afwImage.VisitInfo(*item)
162 v1.writeFits(tablePath)
163 v2 = afwImage.VisitInfo.readFits(tablePath)
164 self.assertEqual(v1, v2)
165 os.unlink(tablePath)
167 def testSetVisitInfoMetadata(self):
168 for item in (self.data1, self.data2):
169 visitInfo = afwImage.VisitInfo(item.exposureId,
170 item.exposureTime,
171 item.darkTime,
172 item.date,
173 item.ut1,
174 item.era,
175 item.boresightRaDec,
176 item.boresightAzAlt,
177 item.boresightAirmass,
178 item.boresightRotAngle,
179 item.rotType,
180 item.observatory,
181 item.weather,
182 )
183 metadata = PropertyList()
184 afwImage.setVisitInfoMetadata(metadata, visitInfo)
185 self.assertEqual(metadata.nameCount(), 20)
186 self.assertEqual(metadata.getScalar("EXPID"), item.exposureId)
187 self.assertEqual(metadata.getScalar("EXPTIME"), item.exposureTime)
188 self.assertEqual(metadata.getScalar("DARKTIME"), item.darkTime)
189 self.assertEqual(metadata.getScalar("DATE-AVG"),
190 item.date.toString(DateTime.TAI))
191 self.assertEqual(metadata.getScalar("TIMESYS"), "TAI")
192 self.assertEqual(metadata.getScalar("MJD-AVG-UT1"), item.ut1)
193 self.assertEqual(metadata.getScalar("AVG-ERA"), item.era.asDegrees())
194 self.assertEqual(metadata.getScalar("BORE-RA"),
195 item.boresightRaDec[0].asDegrees())
196 self.assertEqual(metadata.getScalar("BORE-DEC"),
197 item.boresightRaDec[1].asDegrees())
198 self.assertEqual(metadata.getScalar("BORE-AZ"),
199 item.boresightAzAlt[0].asDegrees())
200 self.assertEqual(metadata.getScalar("BORE-ALT"),
201 item.boresightAzAlt[1].asDegrees())
202 self.assertEqual(metadata.getScalar("BORE-AIRMASS"),
203 item.boresightAirmass)
204 self.assertEqual(metadata.getScalar("BORE-ROTANG"),
205 item.boresightRotAngle.asDegrees())
206 self.assertEqual(metadata.getScalar("ROTTYPE"),
207 RotTypeEnumNameDict[item.rotType])
208 self.assertEqual(metadata.getScalar("OBS-LONG"),
209 item.observatory.getLongitude().asDegrees())
210 self.assertEqual(metadata.getScalar("OBS-LAT"),
211 item.observatory.getLatitude().asDegrees())
212 self.assertEqual(metadata.getScalar("OBS-ELEV"),
213 item.observatory.getElevation())
214 self.assertEqual(metadata.getScalar("AIRTEMP"),
215 item.weather.getAirTemperature())
216 self.assertEqual(metadata.getScalar("AIRPRESS"),
217 item.weather.getAirPressure())
218 self.assertEqual(metadata.getScalar("HUMIDITY"),
219 item.weather.getHumidity())
221 def testSetVisitInfoMetadataMissingValues(self):
222 """If a value is unknown then it should not be written to the metadata"""
223 visitInfo = afwImage.VisitInfo() # only rot type is known
224 metadata = PropertyList()
225 afwImage.setVisitInfoMetadata(metadata, visitInfo)
226 self.assertEqual(metadata.getScalar("ROTTYPE"),
227 RotTypeEnumNameDict[afwImage.RotType.UNKNOWN])
228 self.assertEqual(metadata.nameCount(), 1)
230 def testStripVisitInfoKeywords(self):
231 for argList in (self.data1, self.data2):
232 visitInfo = afwImage.VisitInfo(*argList)
233 metadata = PropertyList()
234 afwImage.setVisitInfoMetadata(metadata, visitInfo)
235 # add an extra keyword that will not be stripped
236 metadata.set("EXTRA", 5)
237 self.assertEqual(metadata.nameCount(), 21)
238 afwImage.stripVisitInfoKeywords(metadata)
239 self.assertEqual(metadata.nameCount(), 1)
241 def _testIsEmpty(self, visitInfo):
242 """Test that visitInfo is all NaN or 0, as appropriate."""
243 self.assertEqual(visitInfo.getExposureId(), 0)
244 self.assertTrue(math.isnan(visitInfo.getExposureTime()))
245 self.assertTrue(math.isnan(visitInfo.getDarkTime()))
246 self.assertEqual(visitInfo.getDate(), DateTime())
247 self.assertTrue(math.isnan(visitInfo.getUt1()))
248 self.assertTrue(math.isnan(visitInfo.getEra().asDegrees()))
249 for i in range(2):
250 self.assertTrue(math.isnan(
251 visitInfo.getBoresightRaDec()[i].asDegrees()))
252 self.assertTrue(math.isnan(
253 visitInfo.getBoresightAzAlt()[i].asDegrees()))
254 self.assertTrue(math.isnan(visitInfo.getBoresightAirmass()))
255 self.assertTrue(math.isnan(
256 visitInfo.getBoresightRotAngle().asDegrees()))
257 self.assertEqual(visitInfo.getRotType(), afwImage.RotType.UNKNOWN)
258 self.assertTrue(math.isnan(
259 visitInfo.getObservatory().getLongitude().asDegrees()))
260 self.assertTrue(math.isnan(
261 visitInfo.getObservatory().getLatitude().asDegrees()))
262 self.assertTrue(math.isnan(visitInfo.getObservatory().getElevation()))
263 self.assertTrue(math.isnan(visitInfo.getWeather().getAirTemperature()))
264 self.assertTrue(math.isnan(visitInfo.getWeather().getAirPressure()))
265 self.assertTrue(math.isnan(visitInfo.getWeather().getHumidity()))
266 self.assertTrue(math.isnan(visitInfo.getBoresightHourAngle()))
268 def testMetadataConstructor(self):
269 """Test the metadata constructor
271 This constructor allows missing values
272 """
273 data = self.data1
275 metadata = propertySetFromDict({})
276 visitInfo = afwImage.VisitInfo(metadata)
277 self._testIsEmpty(visitInfo)
279 metadata = propertySetFromDict({"EXPID": data.exposureId})
280 visitInfo = afwImage.VisitInfo(metadata)
281 self.assertEqual(visitInfo.getExposureId(), data.exposureId)
282 self.assertTrue(math.isnan(visitInfo.getExposureTime()))
284 metadata = propertySetFromDict({"EXPTIME": data.exposureTime})
285 visitInfo = afwImage.VisitInfo(metadata)
286 self.assertEqual(visitInfo.getExposureTime(), data.exposureTime)
288 metadata = propertySetFromDict({"DARKTIME": data.darkTime})
289 visitInfo = afwImage.VisitInfo(metadata)
290 self.assertEqual(visitInfo.getDarkTime(), data.darkTime)
292 metadata = propertySetFromDict(
293 {"DATE-AVG": data.date.toString(DateTime.TAI), "TIMESYS": "TAI"})
294 visitInfo = afwImage.VisitInfo(metadata)
295 self.assertEqual(visitInfo.getDate(), data.date)
297 # TIME-MID in UTC is an acceptable alternative to DATE-AVG
298 metadata = propertySetFromDict(
299 {"TIME-MID": data.date.toString(DateTime.UTC)})
300 visitInfo = afwImage.VisitInfo(metadata)
301 self.assertEqual(visitInfo.getDate(), data.date)
303 # TIME-MID must be in UTC and TIMESYS is ignored
304 metadata = propertySetFromDict({
305 "TIME-MID": data.date.toString(DateTime.TAI) + "Z",
306 "TIMESYS": "TAI",
307 })
308 visitInfo = afwImage.VisitInfo(metadata)
309 self.assertNotEqual(visitInfo.getDate(), data.date)
311 # if both DATE-AVG and TIME-MID provided then use DATE-AVG
312 # use the wrong time system for TIME-MID so if it is used, an error
313 # will result
314 metadata = propertySetFromDict({
315 "DATE-AVG": data.date.toString(DateTime.TAI),
316 "TIMESYS": "TAI",
317 "TIME-MID": data.date.toString(DateTime.TAI) + "Z",
318 })
319 visitInfo = afwImage.VisitInfo(metadata)
320 self.assertEqual(visitInfo.getDate(), data.date)
322 metadata = propertySetFromDict({"MJD-AVG-UT1": data.ut1})
323 visitInfo = afwImage.VisitInfo(metadata)
324 self.assertEqual(visitInfo.getUt1(), data.ut1)
326 metadata = propertySetFromDict({"AVG-ERA": data.era.asDegrees()})
327 visitInfo = afwImage.VisitInfo(metadata)
328 self.assertEqual(visitInfo.getEra(), data.era)
330 for i, key in enumerate(("BORE-RA", "BORE-DEC")):
331 metadata = propertySetFromDict(
332 {key: data.boresightRaDec[i].asDegrees()})
333 visitInfo = afwImage.VisitInfo(metadata)
334 self.assertEqual(visitInfo.getBoresightRaDec()
335 [i], data.boresightRaDec[i])
337 for i, key in enumerate(("BORE-AZ", "BORE-ALT")):
338 metadata = propertySetFromDict(
339 {key: data.boresightAzAlt[i].asDegrees()})
340 visitInfo = afwImage.VisitInfo(metadata)
341 self.assertEqual(visitInfo.getBoresightAzAlt()
342 [i], data.boresightAzAlt[i])
344 metadata = propertySetFromDict({"BORE-AIRMASS": data.boresightAirmass})
345 visitInfo = afwImage.VisitInfo(metadata)
346 self.assertEqual(visitInfo.getBoresightAirmass(),
347 data.boresightAirmass)
349 metadata = propertySetFromDict(
350 {"BORE-ROTANG": data.boresightRotAngle.asDegrees()})
351 visitInfo = afwImage.VisitInfo(metadata)
352 self.assertEqual(visitInfo.getBoresightRotAngle(),
353 data.boresightRotAngle)
355 metadata = propertySetFromDict(
356 {"ROTTYPE": RotTypeEnumNameDict[data.rotType]})
357 visitInfo = afwImage.VisitInfo(metadata)
358 self.assertEqual(visitInfo.getRotType(), data.rotType)
360 metadata = propertySetFromDict(
361 {"OBS-LONG": data.observatory.getLongitude().asDegrees()})
362 visitInfo = afwImage.VisitInfo(metadata)
363 self.assertEqual(visitInfo.getObservatory().getLongitude(),
364 data.observatory.getLongitude())
366 metadata = propertySetFromDict(
367 {"OBS-LAT": data.observatory.getLatitude().asDegrees()})
368 visitInfo = afwImage.VisitInfo(metadata)
369 self.assertEqual(visitInfo.getObservatory().getLatitude(),
370 data.observatory.getLatitude())
372 metadata = propertySetFromDict(
373 {"OBS-ELEV": data.observatory.getElevation()})
374 visitInfo = afwImage.VisitInfo(metadata)
375 self.assertEqual(visitInfo.getObservatory().getElevation(),
376 data.observatory.getElevation())
378 metadata = propertySetFromDict(
379 {"AIRTEMP": data.weather.getAirTemperature()})
380 visitInfo = afwImage.VisitInfo(metadata)
381 self.assertEqual(visitInfo.getWeather().getAirTemperature(),
382 data.weather.getAirTemperature())
384 metadata = propertySetFromDict(
385 {"AIRPRESS": data.weather.getAirPressure()})
386 visitInfo = afwImage.VisitInfo(metadata)
387 self.assertEqual(visitInfo.getWeather().getAirPressure(),
388 data.weather.getAirPressure())
390 metadata = propertySetFromDict(
391 {"HUMIDITY": data.weather.getHumidity()})
392 visitInfo = afwImage.VisitInfo(metadata)
393 self.assertEqual(visitInfo.getWeather().getHumidity(),
394 data.weather.getHumidity())
396 def testConstructorKeywordArguments(self):
397 """Test VisitInfo with named arguments"""
398 data = self.data1
400 visitInfo = afwImage.VisitInfo()
401 self._testIsEmpty(visitInfo)
403 visitInfo = afwImage.VisitInfo(exposureId=data.exposureId)
404 self.assertEqual(visitInfo.getExposureId(), data.exposureId)
405 self.assertTrue(math.isnan(visitInfo.getExposureTime()))
407 visitInfo = afwImage.VisitInfo(exposureTime=data.exposureTime)
408 self.assertEqual(visitInfo.getExposureTime(), data.exposureTime)
410 visitInfo = afwImage.VisitInfo(darkTime=data.darkTime)
411 self.assertEqual(visitInfo.getDarkTime(), data.darkTime)
413 visitInfo = afwImage.VisitInfo(date=data.date)
414 self.assertEqual(visitInfo.getDate(), data.date)
416 visitInfo = afwImage.VisitInfo(ut1=data.ut1)
417 self.assertEqual(visitInfo.getUt1(), data.ut1)
419 visitInfo = afwImage.VisitInfo(era=data.era)
420 self.assertEqual(visitInfo.getEra(), data.era)
422 visitInfo = afwImage.VisitInfo(boresightRaDec=data.boresightRaDec)
423 self.assertEqual(visitInfo.getBoresightRaDec(), data.boresightRaDec)
425 visitInfo = afwImage.VisitInfo(boresightAzAlt=data.boresightAzAlt)
426 self.assertEqual(visitInfo.getBoresightAzAlt(), data.boresightAzAlt)
428 visitInfo = afwImage.VisitInfo(boresightAirmass=data.boresightAirmass)
429 self.assertEqual(visitInfo.getBoresightAirmass(),
430 data.boresightAirmass)
432 visitInfo = afwImage.VisitInfo(
433 boresightRotAngle=data.boresightRotAngle)
434 self.assertEqual(visitInfo.getBoresightRotAngle(),
435 data.boresightRotAngle)
437 visitInfo = afwImage.VisitInfo(rotType=data.rotType)
438 self.assertEqual(visitInfo.getRotType(), data.rotType)
440 visitInfo = afwImage.VisitInfo(observatory=data.observatory)
441 self.assertEqual(visitInfo.getObservatory(), data.observatory)
443 visitInfo = afwImage.VisitInfo(weather=data.weather)
444 self.assertEqual(visitInfo.getWeather(), data.weather)
446 def testGoodRotTypes(self):
447 """Test round trip of all valid rot types"""
448 for rotType in RotTypeEnumNameDict:
449 metadata = propertySetFromDict(
450 {"ROTTYPE": RotTypeEnumNameDict[rotType]})
451 visitInfo = afwImage.VisitInfo(metadata)
452 self.assertEqual(visitInfo.getRotType(), rotType)
454 def testBadRotTypes(self):
455 """Test that invalid rot type names cannot be used to construct a VisitInfo"""
456 for badRotTypeName in (
457 "unknown", # must be all uppercase
458 "sky", # must be all uppercase
459 "Sky", # must be all uppercase
460 "SKY1", # extra chars
461 "HORIZONTAL", # extra chars
462 ):
463 metadata = propertySetFromDict({"ROTTYPE": badRotTypeName})
464 with self.assertRaises(lsst.pex.exceptions.RuntimeError):
465 afwImage.VisitInfo(metadata)
467 def test_str(self):
468 """Check that we get something reasonable for str()"""
469 visitInfo = afwImage.VisitInfo(self.data1.exposureId,
470 self.data1.exposureTime,
471 self.data1.darkTime,
472 self.data1.date,
473 self.data1.ut1,
474 self.data1.era,
475 self.data1.boresightRaDec,
476 self.data1.boresightAzAlt,
477 self.data1.boresightAirmass,
478 self.data1.boresightRotAngle,
479 self.data1.rotType,
480 self.data1.observatory,
481 self.data1.weather,
482 )
483 string = str(visitInfo)
484 self.assertIn("exposureId=10313423", string)
485 self.assertIn("exposureTime=10.01", string)
486 self.assertIn("darkTime=11.02", string)
487 self.assertIn("rotType=1", string)
489 def testParallacticAngle(self):
490 """Check that we get the same precomputed values for parallactic angle."""
491 parallacticAngle = [141.39684140703142*degrees, 76.99982166973487*degrees]
492 for item, parAngle in zip((self.data1, self.data2), parallacticAngle):
493 visitInfo = afwImage.VisitInfo(era=item.era,
494 boresightRaDec=item.boresightRaDec,
495 observatory=item.observatory,
496 )
497 self.assertAnglesAlmostEqual(visitInfo.getBoresightParAngle(), parAngle)
499 def testParallacticAngleNorthMeridian(self):
500 """An observation on the Meridian that is North of zenith has a parallactic angle of pi radians."""
501 meridianBoresightRA = self.data1.era + self.data1.observatory.getLongitude()
502 northBoresightDec = self.data1.observatory.getLatitude() + 10.*degrees
503 visitInfo = afwImage.VisitInfo(era=self.data1.era,
504 boresightRaDec=SpherePoint(meridianBoresightRA,
505 northBoresightDec),
506 observatory=self.data1.observatory,
507 )
508 self.assertAnglesAlmostEqual(visitInfo.getBoresightParAngle(), Angle(np.pi))
510 def testParallacticAngleSouthMeridian(self):
511 """An observation on the Meridian that is South of zenith has a parallactic angle of zero."""
512 meridianBoresightRA = self.data1.era + self.data1.observatory.getLongitude()
513 southBoresightDec = self.data1.observatory.getLatitude() - 10.*degrees
514 visitInfo = afwImage.VisitInfo(era=self.data1.era,
515 boresightRaDec=SpherePoint(meridianBoresightRA,
516 southBoresightDec),
517 observatory=self.data1.observatory,
518 )
519 self.assertAnglesAlmostEqual(visitInfo.getBoresightParAngle(), Angle(0.))
522def setup_module(module):
523 lsst.utils.tests.init()
526class MemoryTester(lsst.utils.tests.MemoryTestCase):
527 pass
530if __name__ == "__main__": 530 ↛ 531line 530 didn't jump to line 531, because the condition on line 530 was never true
531 lsst.utils.tests.init()
532 unittest.main()