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
51def makeVisitInfo(data):
52 """Return a VisitInfo constructed from a VisitInfoData namedtuple."""
53 return afwImage.VisitInfo(data.exposureId,
54 data.exposureTime,
55 data.darkTime,
56 data.date,
57 data.ut1,
58 data.era,
59 data.boresightRaDec,
60 data.boresightAzAlt,
61 data.boresightAirmass,
62 data.boresightRotAngle,
63 data.rotType,
64 data.observatory,
65 data.weather,
66 data.instrumentLabel
67 )
70class VisitInfoTestCase(lsst.utils.tests.TestCase):
71 """Test lsst.afw.image.VisitInfo, a simple struct-like class"""
73 def setUp(self):
74 self.testDir = os.path.dirname(__file__)
76 def computeLstHA(data):
77 """Return LST, Hour Angle, computed from VisitInfoData."""
78 localEra = data.era + data.observatory.getLongitude()
79 hourAngle = localEra - data.boresightRaDec[0]
80 return localEra, hourAngle
82 fields = ['exposureId',
83 'exposureTime',
84 'darkTime',
85 'date',
86 'ut1',
87 'era',
88 'boresightRaDec',
89 'boresightAzAlt',
90 'boresightAirmass',
91 'boresightRotAngle',
92 'rotType',
93 'observatory',
94 'weather',
95 'instrumentLabel'
96 ]
97 VisitInfoData = collections.namedtuple("VisitInfoData", fields)
98 data1 = VisitInfoData(exposureId=10313423,
99 exposureTime=10.01,
100 darkTime=11.02,
101 date=DateTime(
102 65321.1, DateTime.MJD, DateTime.TAI),
103 ut1=12345.1,
104 era=45.1*degrees,
105 boresightRaDec=SpherePoint(
106 23.1*degrees, 73.2*degrees),
107 boresightAzAlt=SpherePoint(
108 134.5*degrees, 33.3*degrees),
109 boresightAirmass=1.73,
110 boresightRotAngle=73.2*degrees,
111 rotType=afwImage.RotType.SKY,
112 observatory=Observatory(
113 11.1*degrees, 22.2*degrees, 0.333),
114 weather=Weather(1.1, 2.2, 34.5),
115 instrumentLabel="TestCameraOne",
116 )
117 self.data1 = data1
118 self.localEra1, self.hourAngle1 = computeLstHA(data1)
119 data2 = VisitInfoData(exposureId=1,
120 exposureTime=15.5,
121 darkTime=17.8,
122 date=DateTime(
123 55321.2, DateTime.MJD, DateTime.TAI),
124 ut1=312345.1,
125 era=25.1*degrees,
126 boresightRaDec=SpherePoint(
127 2.1*degrees, 33.2*degrees),
128 boresightAzAlt=SpherePoint(13.5*degrees, 83.3*degrees),
129 boresightAirmass=2.05,
130 boresightRotAngle=-53.2*degrees,
131 rotType=afwImage.RotType.HORIZON,
132 observatory=Observatory(
133 22.2*degrees, 33.3*degrees, 0.444),
134 weather=Weather(2.2, 3.3, 44.4),
135 instrumentLabel="TestCameraTwo"
136 )
137 self.data2 = data2
138 self.localEra2, self.hourAngle2 = computeLstHA(data2)
140 def _testValueConstructor(self, data, localEra, hourAngle):
141 visitInfo = makeVisitInfo(data)
142 self.assertEqual(visitInfo.getExposureId(), data.exposureId)
143 self.assertEqual(visitInfo.getExposureTime(), data.exposureTime)
144 self.assertEqual(visitInfo.getDarkTime(), data.darkTime)
145 self.assertEqual(visitInfo.getDate(), data.date)
146 self.assertEqual(visitInfo.getUt1(), data.ut1)
147 self.assertEqual(visitInfo.getEra(), data.era)
148 self.assertEqual(visitInfo.getBoresightRaDec(), data.boresightRaDec)
149 self.assertEqual(visitInfo.getBoresightAzAlt(), data.boresightAzAlt)
150 self.assertEqual(visitInfo.getBoresightAirmass(),
151 data.boresightAirmass)
152 self.assertEqual(visitInfo.getBoresightRotAngle(),
153 data.boresightRotAngle)
154 self.assertEqual(visitInfo.getRotType(), data.rotType)
155 self.assertEqual(visitInfo.getObservatory(), data.observatory)
156 self.assertEqual(visitInfo.getInstrumentLabel(), data.instrumentLabel)
157 self.assertEqual(visitInfo.getWeather(), data.weather)
158 self.assertEqual(visitInfo.getLocalEra(), localEra)
159 self.assertEqual(visitInfo.getBoresightHourAngle(), hourAngle)
161 def testValueConstructor_data1(self):
162 self._testValueConstructor(self.data1, self.localEra1, self.hourAngle1)
164 def testValueConstructor_data2(self):
165 self._testValueConstructor(self.data2, self.localEra2, self.hourAngle2)
167 def testTablePersistence(self):
168 for item in (self.data1, self.data2):
169 tablePath = os.path.join(
170 self.testDir, "testVisitInfo_testTablePersistence.fits")
171 v1 = afwImage.VisitInfo(*item)
172 v1.writeFits(tablePath)
173 v2 = afwImage.VisitInfo.readFits(tablePath)
174 self.assertEqual(v1, v2)
175 os.unlink(tablePath)
177 def testSetVisitInfoMetadata(self):
178 for item in (self.data1, self.data2):
179 visitInfo = makeVisitInfo(item)
180 metadata = PropertyList()
181 afwImage.setVisitInfoMetadata(metadata, visitInfo)
182 self.assertEqual(metadata.nameCount(), 21)
183 self.assertEqual(metadata.getScalar("EXPID"), item.exposureId)
184 self.assertEqual(metadata.getScalar("EXPTIME"), item.exposureTime)
185 self.assertEqual(metadata.getScalar("DARKTIME"), item.darkTime)
186 self.assertEqual(metadata.getScalar("DATE-AVG"),
187 item.date.toString(DateTime.TAI))
188 self.assertEqual(metadata.getScalar("TIMESYS"), "TAI")
189 self.assertEqual(metadata.getScalar("MJD-AVG-UT1"), item.ut1)
190 self.assertEqual(metadata.getScalar("AVG-ERA"), item.era.asDegrees())
191 self.assertEqual(metadata.getScalar("BORE-RA"),
192 item.boresightRaDec[0].asDegrees())
193 self.assertEqual(metadata.getScalar("BORE-DEC"),
194 item.boresightRaDec[1].asDegrees())
195 self.assertEqual(metadata.getScalar("BORE-AZ"),
196 item.boresightAzAlt[0].asDegrees())
197 self.assertEqual(metadata.getScalar("BORE-ALT"),
198 item.boresightAzAlt[1].asDegrees())
199 self.assertEqual(metadata.getScalar("BORE-AIRMASS"),
200 item.boresightAirmass)
201 self.assertEqual(metadata.getScalar("BORE-ROTANG"),
202 item.boresightRotAngle.asDegrees())
203 self.assertEqual(metadata.getScalar("ROTTYPE"),
204 RotTypeEnumNameDict[item.rotType])
205 self.assertEqual(metadata.getScalar("OBS-LONG"),
206 item.observatory.getLongitude().asDegrees())
207 self.assertEqual(metadata.getScalar("OBS-LAT"),
208 item.observatory.getLatitude().asDegrees())
209 self.assertEqual(metadata.getScalar("OBS-ELEV"),
210 item.observatory.getElevation())
211 self.assertEqual(metadata.getScalar("AIRTEMP"),
212 item.weather.getAirTemperature())
213 self.assertEqual(metadata.getScalar("AIRPRESS"),
214 item.weather.getAirPressure())
215 self.assertEqual(metadata.getScalar("HUMIDITY"),
216 item.weather.getHumidity())
217 self.assertEqual(metadata.getScalar("INSTRUMENT"),
218 item.instrumentLabel)
220 def testSetVisitInfoMetadataMissingValues(self):
221 """If a value is unknown then it should not be written to the metadata"""
222 visitInfo = afwImage.VisitInfo() # only rot type is known
223 metadata = PropertyList()
224 afwImage.setVisitInfoMetadata(metadata, visitInfo)
225 self.assertEqual(metadata.getScalar("ROTTYPE"),
226 RotTypeEnumNameDict[afwImage.RotType.UNKNOWN])
227 self.assertEqual(metadata.nameCount(), 1)
229 def testStripVisitInfoKeywords(self):
230 for argList in (self.data1, self.data2):
231 visitInfo = afwImage.VisitInfo(*argList)
232 metadata = PropertyList()
233 afwImage.setVisitInfoMetadata(metadata, visitInfo)
234 # add an extra keyword that will not be stripped
235 metadata.set("EXTRA", 5)
236 self.assertEqual(metadata.nameCount(), 22)
237 afwImage.stripVisitInfoKeywords(metadata)
238 self.assertEqual(metadata.nameCount(), 1)
240 def _testIsEmpty(self, visitInfo):
241 """Test that visitInfo is all NaN, 0, or empty string, as appropriate.
242 """
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()))
267 self.assertEqual(visitInfo.getInstrumentLabel(), "")
269 def testMetadataConstructor(self):
270 """Test the metadata constructor
272 This constructor allows missing values
273 """
274 data = self.data1
276 metadata = propertySetFromDict({})
277 visitInfo = afwImage.VisitInfo(metadata)
278 self._testIsEmpty(visitInfo)
280 metadata = propertySetFromDict({"EXPID": data.exposureId})
281 visitInfo = afwImage.VisitInfo(metadata)
282 self.assertEqual(visitInfo.getExposureId(), data.exposureId)
283 self.assertTrue(math.isnan(visitInfo.getExposureTime()))
285 metadata = propertySetFromDict({"EXPTIME": data.exposureTime})
286 visitInfo = afwImage.VisitInfo(metadata)
287 self.assertEqual(visitInfo.getExposureTime(), data.exposureTime)
289 metadata = propertySetFromDict({"DARKTIME": data.darkTime})
290 visitInfo = afwImage.VisitInfo(metadata)
291 self.assertEqual(visitInfo.getDarkTime(), data.darkTime)
293 metadata = propertySetFromDict(
294 {"DATE-AVG": data.date.toString(DateTime.TAI), "TIMESYS": "TAI"})
295 visitInfo = afwImage.VisitInfo(metadata)
296 self.assertEqual(visitInfo.getDate(), data.date)
298 # TIME-MID in UTC is an acceptable alternative to DATE-AVG
299 metadata = propertySetFromDict(
300 {"TIME-MID": data.date.toString(DateTime.UTC)})
301 visitInfo = afwImage.VisitInfo(metadata)
302 self.assertEqual(visitInfo.getDate(), data.date)
304 # TIME-MID must be in UTC and TIMESYS is ignored
305 metadata = propertySetFromDict({
306 "TIME-MID": data.date.toString(DateTime.TAI) + "Z",
307 "TIMESYS": "TAI",
308 })
309 visitInfo = afwImage.VisitInfo(metadata)
310 self.assertNotEqual(visitInfo.getDate(), data.date)
312 # if both DATE-AVG and TIME-MID provided then use DATE-AVG
313 # use the wrong time system for TIME-MID so if it is used, an error
314 # will result
315 metadata = propertySetFromDict({
316 "DATE-AVG": data.date.toString(DateTime.TAI),
317 "TIMESYS": "TAI",
318 "TIME-MID": data.date.toString(DateTime.TAI) + "Z",
319 })
320 visitInfo = afwImage.VisitInfo(metadata)
321 self.assertEqual(visitInfo.getDate(), data.date)
323 metadata = propertySetFromDict({"MJD-AVG-UT1": data.ut1})
324 visitInfo = afwImage.VisitInfo(metadata)
325 self.assertEqual(visitInfo.getUt1(), data.ut1)
327 metadata = propertySetFromDict({"AVG-ERA": data.era.asDegrees()})
328 visitInfo = afwImage.VisitInfo(metadata)
329 self.assertEqual(visitInfo.getEra(), data.era)
331 for i, key in enumerate(("BORE-RA", "BORE-DEC")):
332 metadata = propertySetFromDict(
333 {key: data.boresightRaDec[i].asDegrees()})
334 visitInfo = afwImage.VisitInfo(metadata)
335 self.assertEqual(visitInfo.getBoresightRaDec()
336 [i], data.boresightRaDec[i])
338 for i, key in enumerate(("BORE-AZ", "BORE-ALT")):
339 metadata = propertySetFromDict(
340 {key: data.boresightAzAlt[i].asDegrees()})
341 visitInfo = afwImage.VisitInfo(metadata)
342 self.assertEqual(visitInfo.getBoresightAzAlt()
343 [i], data.boresightAzAlt[i])
345 metadata = propertySetFromDict({"BORE-AIRMASS": data.boresightAirmass})
346 visitInfo = afwImage.VisitInfo(metadata)
347 self.assertEqual(visitInfo.getBoresightAirmass(),
348 data.boresightAirmass)
350 metadata = propertySetFromDict(
351 {"BORE-ROTANG": data.boresightRotAngle.asDegrees()})
352 visitInfo = afwImage.VisitInfo(metadata)
353 self.assertEqual(visitInfo.getBoresightRotAngle(),
354 data.boresightRotAngle)
356 metadata = propertySetFromDict(
357 {"ROTTYPE": RotTypeEnumNameDict[data.rotType]})
358 visitInfo = afwImage.VisitInfo(metadata)
359 self.assertEqual(visitInfo.getRotType(), data.rotType)
361 metadata = propertySetFromDict(
362 {"OBS-LONG": data.observatory.getLongitude().asDegrees()})
363 visitInfo = afwImage.VisitInfo(metadata)
364 self.assertEqual(visitInfo.getObservatory().getLongitude(),
365 data.observatory.getLongitude())
367 metadata = propertySetFromDict(
368 {"OBS-LAT": data.observatory.getLatitude().asDegrees()})
369 visitInfo = afwImage.VisitInfo(metadata)
370 self.assertEqual(visitInfo.getObservatory().getLatitude(),
371 data.observatory.getLatitude())
373 metadata = propertySetFromDict(
374 {"OBS-ELEV": data.observatory.getElevation()})
375 visitInfo = afwImage.VisitInfo(metadata)
376 self.assertEqual(visitInfo.getObservatory().getElevation(),
377 data.observatory.getElevation())
379 metadata = propertySetFromDict(
380 {"AIRTEMP": data.weather.getAirTemperature()})
381 visitInfo = afwImage.VisitInfo(metadata)
382 self.assertEqual(visitInfo.getWeather().getAirTemperature(),
383 data.weather.getAirTemperature())
385 metadata = propertySetFromDict(
386 {"AIRPRESS": data.weather.getAirPressure()})
387 visitInfo = afwImage.VisitInfo(metadata)
388 self.assertEqual(visitInfo.getWeather().getAirPressure(),
389 data.weather.getAirPressure())
391 metadata = propertySetFromDict(
392 {"HUMIDITY": data.weather.getHumidity()})
393 visitInfo = afwImage.VisitInfo(metadata)
394 self.assertEqual(visitInfo.getWeather().getHumidity(),
395 data.weather.getHumidity())
397 metadata = propertySetFromDict({"INSTRUMENT": data.instrumentLabel})
398 visitInfo = afwImage.VisitInfo(metadata)
399 self.assertEqual(visitInfo.getInstrumentLabel(), data.instrumentLabel)
401 def testConstructorKeywordArguments(self):
402 """Test VisitInfo with named arguments"""
403 data = self.data1
405 visitInfo = afwImage.VisitInfo()
406 self._testIsEmpty(visitInfo)
408 visitInfo = afwImage.VisitInfo(exposureId=data.exposureId)
409 self.assertEqual(visitInfo.getExposureId(), data.exposureId)
410 self.assertTrue(math.isnan(visitInfo.getExposureTime()))
412 visitInfo = afwImage.VisitInfo(exposureTime=data.exposureTime)
413 self.assertEqual(visitInfo.getExposureTime(), data.exposureTime)
415 visitInfo = afwImage.VisitInfo(darkTime=data.darkTime)
416 self.assertEqual(visitInfo.getDarkTime(), data.darkTime)
418 visitInfo = afwImage.VisitInfo(date=data.date)
419 self.assertEqual(visitInfo.getDate(), data.date)
421 visitInfo = afwImage.VisitInfo(ut1=data.ut1)
422 self.assertEqual(visitInfo.getUt1(), data.ut1)
424 visitInfo = afwImage.VisitInfo(era=data.era)
425 self.assertEqual(visitInfo.getEra(), data.era)
427 visitInfo = afwImage.VisitInfo(boresightRaDec=data.boresightRaDec)
428 self.assertEqual(visitInfo.getBoresightRaDec(), data.boresightRaDec)
430 visitInfo = afwImage.VisitInfo(boresightAzAlt=data.boresightAzAlt)
431 self.assertEqual(visitInfo.getBoresightAzAlt(), data.boresightAzAlt)
433 visitInfo = afwImage.VisitInfo(boresightAirmass=data.boresightAirmass)
434 self.assertEqual(visitInfo.getBoresightAirmass(),
435 data.boresightAirmass)
437 visitInfo = afwImage.VisitInfo(
438 boresightRotAngle=data.boresightRotAngle)
439 self.assertEqual(visitInfo.getBoresightRotAngle(),
440 data.boresightRotAngle)
442 visitInfo = afwImage.VisitInfo(rotType=data.rotType)
443 self.assertEqual(visitInfo.getRotType(), data.rotType)
445 visitInfo = afwImage.VisitInfo(observatory=data.observatory)
446 self.assertEqual(visitInfo.getObservatory(), data.observatory)
448 visitInfo = afwImage.VisitInfo(weather=data.weather)
449 self.assertEqual(visitInfo.getWeather(), data.weather)
451 visitInfo = afwImage.VisitInfo(instrumentLabel=data.instrumentLabel)
452 self.assertEqual(visitInfo.getInstrumentLabel(), data.instrumentLabel)
454 def testGoodRotTypes(self):
455 """Test round trip of all valid rot types"""
456 for rotType in RotTypeEnumNameDict:
457 metadata = propertySetFromDict(
458 {"ROTTYPE": RotTypeEnumNameDict[rotType]})
459 visitInfo = afwImage.VisitInfo(metadata)
460 self.assertEqual(visitInfo.getRotType(), rotType)
462 def testBadRotTypes(self):
463 """Test that invalid rot type names cannot be used to construct a VisitInfo"""
464 for badRotTypeName in (
465 "unknown", # must be all uppercase
466 "sky", # must be all uppercase
467 "Sky", # must be all uppercase
468 "SKY1", # extra chars
469 "HORIZONTAL", # extra chars
470 ):
471 metadata = propertySetFromDict({"ROTTYPE": badRotTypeName})
472 with self.assertRaises(lsst.pex.exceptions.RuntimeError):
473 afwImage.VisitInfo(metadata)
475 def test_str(self):
476 """Check that we get something reasonable for str()"""
477 visitInfo = makeVisitInfo(self.data1)
478 string = str(visitInfo)
479 self.assertIn("exposureId=10313423", string)
480 self.assertIn("exposureTime=10.01", string)
481 self.assertIn("darkTime=11.02", string)
482 self.assertIn("rotType=1", string)
484 def testParallacticAngle(self):
485 """Check that we get the same precomputed values for parallactic angle."""
486 parallacticAngle = [141.39684140703142*degrees, 76.99982166973487*degrees]
487 for item, parAngle in zip((self.data1, self.data2), parallacticAngle):
488 visitInfo = afwImage.VisitInfo(era=item.era,
489 boresightRaDec=item.boresightRaDec,
490 observatory=item.observatory,
491 )
492 self.assertAnglesAlmostEqual(visitInfo.getBoresightParAngle(), parAngle)
494 def testParallacticAngleNorthMeridian(self):
495 """An observation on the Meridian that is North of zenith has a parallactic angle of pi radians."""
496 meridianBoresightRA = self.data1.era + self.data1.observatory.getLongitude()
497 northBoresightDec = self.data1.observatory.getLatitude() + 10.*degrees
498 visitInfo = afwImage.VisitInfo(era=self.data1.era,
499 boresightRaDec=SpherePoint(meridianBoresightRA,
500 northBoresightDec),
501 observatory=self.data1.observatory,
502 )
503 self.assertAnglesAlmostEqual(visitInfo.getBoresightParAngle(), Angle(np.pi))
505 def testParallacticAngleSouthMeridian(self):
506 """An observation on the Meridian that is South of zenith has a parallactic angle of zero."""
507 meridianBoresightRA = self.data1.era + self.data1.observatory.getLongitude()
508 southBoresightDec = self.data1.observatory.getLatitude() - 10.*degrees
509 visitInfo = afwImage.VisitInfo(era=self.data1.era,
510 boresightRaDec=SpherePoint(meridianBoresightRA,
511 southBoresightDec),
512 observatory=self.data1.observatory,
513 )
514 self.assertAnglesAlmostEqual(visitInfo.getBoresightParAngle(), Angle(0.))
517def setup_module(module):
518 lsst.utils.tests.init()
521class MemoryTester(lsst.utils.tests.MemoryTestCase):
522 pass
525if __name__ == "__main__": 525 ↛ 526line 525 didn't jump to line 526, because the condition on line 525 was never true
526 lsst.utils.tests.init()
527 unittest.main()