Hide keyboard shortcuts

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# This file is part of obs_base. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (https://www.lsst.org). 

6# See the COPYRIGHT file at the top-level directory of this distribution 

7# for details of code ownership. 

8# 

9# This program is free software: you can redistribute it and/or modify 

10# it under the terms of the GNU General Public License as published by 

11# the Free Software Foundation, either version 3 of the License, or 

12# (at your option) any later version. 

13# 

14# This program is distributed in the hope that it will be useful, 

15# but WITHOUT ANY WARRANTY; without even the implied warranty of 

16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

17# GNU General Public License for more details. 

18# 

19# You should have received a copy of the GNU General Public License 

20# along with this program. If not, see <https://www.gnu.org/licenses/>. 

21 

22import unittest 

23 

24from astropy.coordinates import Angle 

25import astropy.units as u 

26 

27import lsst.utils.tests 

28 

29from astro_metadata_translator import FitsTranslator, StubTranslator 

30from astro_metadata_translator.translators.helpers import tracking_from_degree_headers 

31from lsst.afw.cameraGeom.testUtils import CameraWrapper, DetectorWrapper 

32import lsst.afw.geom 

33import lsst.daf.base 

34import lsst.daf.butler 

35import lsst.geom 

36from lsst.obs.base import FitsRawFormatterBase, MakeRawVisitInfoViaObsInfo, FilterDefinitionCollection 

37from lsst.obs.base.utils import createInitialSkyWcs, InitialSkyWcsError 

38 

39 

40class SimpleTestingTranslator(FitsTranslator, StubTranslator): 

41 _const_map = {"boresight_rotation_angle": Angle(90*u.deg), 

42 "boresight_rotation_coord": "sky", 

43 "detector_exposure_id": 12345, 

44 # The following are defined to prevent warnings about 

45 # undefined translators 

46 "dark_time": 0.0*u.s, 

47 "exposure_time": 0.0*u.s, 

48 "physical_filter": "u", 

49 "detector_num": 0, 

50 "detector_name": "0", 

51 "detector_group": "", 

52 "detector_unique_name": "0", 

53 "detector_serial": "", 

54 "observation_id": "--", 

55 "science_program": "unknown", 

56 "object": "unknown", 

57 "exposure_id": 0, 

58 "visit_id": 0, 

59 "relative_humidity": 30.0, 

60 "pressure": 0.0*u.MPa, 

61 "temperature": 273*u.K, 

62 "altaz_begin": None, 

63 } 

64 _trivial_map = {"boresight_airmass": "AIRMASS", 

65 "observation_type": "OBSTYPE"} 

66 

67 def to_tracking_radec(self): 

68 radecsys = ("RADESYS", ) 

69 radecpairs = (("RA", "DEC"),) 

70 return tracking_from_degree_headers(self, radecsys, radecpairs, unit=(u.deg, u.deg)) 

71 

72 

73class MakeTestingRawVisitInfo(MakeRawVisitInfoViaObsInfo): 

74 metadataTranslator = SimpleTestingTranslator 

75 

76 

77class SimpleFitsRawFormatter(FitsRawFormatterBase): 

78 filterDefinitions = FilterDefinitionCollection() 

79 

80 @property 

81 def translatorClass(self): 

82 return SimpleTestingTranslator 

83 

84 def getDetector(self, id): 

85 """Use CameraWrapper to create a fake detector that can map from 

86 PIXELS to FIELD_ANGLE. 

87 

88 Always return Detector #10, so all the tests are self-consistent. 

89 """ 

90 return CameraWrapper().camera.get(10) 

91 

92 

93class FitsRawFormatterTestCase(lsst.utils.tests.TestCase): 

94 def setUp(self): 

95 # reset the filters before we test anything 

96 FilterDefinitionCollection.reset() 

97 

98 # The FITS WCS and VisitInfo coordinates in this header are 

99 # intentionally different, to make comparisons between them more 

100 # obvious. 

101 self.boresight = lsst.geom.SpherePoint(10., 20., lsst.geom.degrees) 

102 self.header = { 

103 "TELESCOP": "TEST", 

104 "INSTRUME": "UNKNOWN", 

105 "AIRMASS": 1.2, 

106 "RADESYS": "ICRS", 

107 "OBSTYPE": "science", 

108 "EQUINOX": 2000, 

109 "OBSGEO-X": "-5464588.84421314", 

110 "OBSGEO-Y": "-2493000.19137644", 

111 "OBSGEO-Z": "2150653.35350771", 

112 "RA": self.boresight.getLatitude().asDegrees(), 

113 "DEC": self.boresight.getLongitude().asDegrees(), 

114 "CTYPE1": "RA---SIN", 

115 "CTYPE2": "DEC--SIN", 

116 "CRPIX1": 5, 

117 "CRPIX2": 6, 

118 "CRVAL1": self.boresight.getLatitude().asDegrees() + 1, 

119 "CRVAL2": self.boresight.getLongitude().asDegrees() + 1, 

120 "CD1_1": 1e-5, 

121 "CD1_2": 0, 

122 "CD2_2": 1e-5, 

123 "CD2_1": 0 

124 } 

125 # make a property list of the above, for use by the formatter. 

126 self.metadata = lsst.daf.base.PropertyList() 

127 self.metadata.update(self.header) 

128 

129 maker = MakeTestingRawVisitInfo() 

130 self.visitInfo = maker(self.header) 

131 

132 self.metadataSkyWcs = lsst.afw.geom.makeSkyWcs(self.metadata, strip=False) 

133 self.boresightSkyWcs = createInitialSkyWcs(self.visitInfo, CameraWrapper().camera.get(10)) 

134 

135 # set these to `contextlib.nullcontext()` to print the log warnings 

136 self.warnContext = self.assertLogs(level="WARNING") 

137 self.logContext = lsst.log.UsePythonLogging() 

138 

139 # Make a data ID to pass to the formatter. 

140 universe = lsst.daf.butler.DimensionUniverse() 

141 dataId = lsst.daf.butler.DataCoordinate.standardize(instrument="Cam1", exposure=2, detector=10, 

142 physical_filter="u", band="u", universe=universe) 

143 

144 # We have no file in these tests, so make an empty descriptor. 

145 fileDescriptor = lsst.daf.butler.FileDescriptor(None, None) 

146 self.formatter = SimpleFitsRawFormatter(fileDescriptor, dataId) 

147 # Force the formatter's metadata to be what we've created above. 

148 self.formatter._metadata = self.metadata 

149 

150 def test_makeWcs(self): 

151 detector = self.formatter.getDetector(1) 

152 wcs = self.formatter.makeWcs(self.visitInfo, detector) 

153 self.assertNotEqual(wcs, self.metadataSkyWcs) 

154 self.assertEqual(wcs, self.boresightSkyWcs) 

155 

156 def test_makeWcs_warn_if_metadata_is_bad(self): 

157 """If the metadata is bad, log a warning and use the VisitInfo WCS. 

158 """ 

159 detector = self.formatter.getDetector(1) 

160 self.metadata.remove("CTYPE1") 

161 with self.warnContext, self.logContext: 

162 wcs = self.formatter.makeWcs(self.visitInfo, detector) 

163 self.assertNotEqual(wcs, self.metadataSkyWcs) 

164 self.assertEqual(wcs, self.boresightSkyWcs) 

165 

166 def test_makeWcs_warn_if_visitInfo_is_None(self): 

167 """If VisitInfo is None, log a warning and use the metadata WCS. 

168 """ 

169 detector = self.formatter.getDetector(1) 

170 with self.warnContext, self.logContext: 

171 wcs = self.formatter.makeWcs(None, detector) 

172 self.assertEqual(wcs, self.metadataSkyWcs) 

173 self.assertNotEqual(wcs, self.boresightSkyWcs) 

174 

175 def test_makeWcs_fail_if_visitInfo_is_None(self): 

176 """If VisitInfo is None and metadata failed, raise an exception. 

177 """ 

178 detector = self.formatter.getDetector(1) 

179 self.metadata.remove("CTYPE1") 

180 with self.warnContext, self.logContext, self.assertRaises(InitialSkyWcsError): 

181 self.formatter.makeWcs(None, detector) 

182 

183 def test_makeWcs_fail_if_detector_is_bad(self): 

184 """If Detector is broken, raise an exception. 

185 """ 

186 # This detector doesn't know about FIELD_ANGLE, so can't be used to 

187 # make a SkyWcs. 

188 detector = DetectorWrapper().detector 

189 with self.assertRaises(InitialSkyWcsError): 

190 self.formatter.makeWcs(self.visitInfo, detector) 

191 

192 

193class MemoryTester(lsst.utils.tests.MemoryTestCase): 

194 pass 

195 

196 

197def setup_module(module): 

198 lsst.utils.tests.init() 

199 

200 

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

202 lsst.utils.tests.init() 

203 unittest.main()