Coverage for tests/test_frameSetUtils.py : 11%

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
1import unittest
3from astropy.coordinates import SkyCoord
5import astshim as ast
6from lsst.geom import arcseconds, degrees, radians, Point2D, SpherePoint
7from lsst.afw.geom import makeCdMatrix
8from lsst.afw.geom.detail import readFitsWcs, readLsstSkyWcs, getPropertyListFromFitsChan
9from lsst.afw.geom.wcsUtils import makeSimpleWcsMetadata
10import lsst.utils.tests
12PrintStrippedNames = False
15class FrameSetUtilsTestCase(lsst.utils.tests.TestCase):
16 """This is sparse because SkyWcs unit tests test much of this package
17 """
19 def setUp(self):
20 # arbitrary values
21 self.crpix = Point2D(100, 100)
22 self.crval = SpherePoint(30 * degrees, 45 * degrees)
23 self.scale = 1.0 * arcseconds
25 def makeMetadata(self):
26 """Return a WCS that is typical for an image
28 It will contain 14 cards that describe a WCS, added by makeSimpleWcsMetadata,
29 plus 25 additional cards, including a set for WCS "A", which is what
30 LSST uses to store xy0, and LTV1 and LTV2, which LSST used to use for xy0
31 """
32 # arbitrary values
33 orientation = 0 * degrees
34 flipX = False
35 metadata = makeSimpleWcsMetadata(
36 crpix=self.crpix,
37 crval=self.crval,
38 cdMatrix=makeCdMatrix(scale=self.scale, orientation=orientation, flipX=flipX),
39 )
40 self.assertEqual(metadata.nameCount(), 11) # 2 CD terms are zero and so are omitted
41 metadata.add("SIMPLE", True)
42 metadata.add("BITPIX", 16)
43 metadata.add("NAXIS", 2)
44 metadata.add("NAXIS1", 500)
45 metadata.add("NAXIS2", 200)
46 metadata.add("BZERO", 32768)
47 metadata.add("BSCALE", 1)
48 metadata.add("TIMESYS", "UTC")
49 metadata.add("UTC-OBS", "12:04:45.73")
50 metadata.add("DATE-OBS", "2006-05-20")
51 metadata.add("EXPTIME", 5.0)
52 metadata.add("COMMENT", "a comment")
53 metadata.add("COMMENT", "another comment")
54 metadata.add("HISTORY", "some history")
55 metadata.add("EXTEND", True)
56 metadata.add("INHERIT", False)
57 metadata.add("CRPIX1A", 1.0)
58 metadata.add("CRPIX2A", 1.0)
59 metadata.add("CRVAL1A", 300)
60 metadata.add("CRVAL2A", 400)
61 metadata.add("CUNIT1A", "pixels")
62 metadata.add("CUNIT2A", "pixels")
63 metadata.add("LTV1", -300)
64 metadata.add("LTV2", -400)
65 metadata.add("ZOTHER", "non-standard")
66 return metadata
68 def getCrpix(self, metadata):
69 """Get CRPIX from metadata using the LSST convention: 0-based in parent coordinates
70 """
71 return Point2D( # zero-based, hence the - 1
72 metadata.getScalar("CRPIX1") + metadata.getScalar("CRVAL1A") - 1,
73 metadata.getScalar("CRPIX2") + metadata.getScalar("CRVAL2A") - 1,
74 )
76 def testIgnoreNan(self):
77 """Test that NaN in a property list does not crash the WCS extraction.
78 """
79 metadata = self.makeMetadata()
80 metadata["ISNAN"] = float("NaN")
82 # This will issue a warning from C++ but that warning can not be
83 # redirected to python logging machinery.
84 # This code causes a SIGABRT without DM-21355 implemented
85 frameSet1 = readFitsWcs(metadata, strip=False)
86 self.assertEqual(type(frameSet1), ast.FrameSet)
88 def testReadFitsWcsStripMetadata(self):
89 metadata = self.makeMetadata()
90 nKeys = len(metadata.toList())
91 nToStrip = 11
92 frameSet1 = readFitsWcs(metadata, strip=False)
93 self.assertEqual(type(frameSet1), ast.FrameSet)
94 self.assertEqual(len(metadata.toList()), nKeys)
96 # read again, this time stripping metadata
97 frameSet2 = readFitsWcs(metadata, strip=True)
98 self.assertEqual(len(metadata.toList()), nKeys - nToStrip)
99 self.assertEqual(frameSet1, frameSet2)
101 # having stripped the metadata, it should not be possible to create a WCS
102 with self.assertRaises(lsst.pex.exceptions.TypeError):
103 readFitsWcs(metadata, strip=False)
105 def testReadFitsWcsFixRadecsys(self):
106 # compare a WCS made with RADESYS against one made with RADECSYS,
107 # using a system other than FK5, since that is the default;
108 # both should be the same because readFitsWcs replaces RADECSYS with RADESYS
109 metadata1 = self.makeMetadata()
110 metadata1.set("RADESYS", "ICRS")
111 frameSet1 = readFitsWcs(metadata1, strip=False)
112 self.assertEqual(metadata1.getScalar("RADESYS"), "ICRS")
114 metadata2 = self.makeMetadata()
115 metadata2.remove("RADESYS")
116 metadata2.set("RADECSYS", "ICRS")
117 frameSet2 = readFitsWcs(metadata2, strip=False)
118 # metadata will have been corrected by readFitsWcs
119 self.assertFalse(metadata2.exists("RADECSYS"))
120 self.assertEqual(metadata2.getScalar("RADESYS"), "ICRS")
121 self.assertEqual(frameSet1, frameSet2)
123 def testReadLsstSkyWcsStripMetadata(self):
124 metadata = self.makeMetadata()
125 nKeys = len(metadata.toList())
126 nToStrip = 11 + 6 # WCS "A" is also stripped
127 frameSet1 = readLsstSkyWcs(metadata, strip=False)
128 self.assertEqual(len(metadata.toList()), nKeys)
130 crval = SpherePoint(metadata.getScalar("CRVAL1"), metadata.getScalar("CRVAL2"), degrees)
131 crvalRad = crval.getPosition(radians)
132 desiredCrpix = self.getCrpix(metadata)
133 computedCrpix = frameSet1.applyInverse(crvalRad)
134 self.assertPairsAlmostEqual(desiredCrpix, computedCrpix)
136 # read again, this time stripping metadata
137 frameSet2 = readLsstSkyWcs(metadata, strip=True)
138 self.assertEqual(len(metadata.toList()), nKeys - nToStrip)
139 self.assertEqual(frameSet1, frameSet2)
141 # having stripped the WCS keywords, we should not be able to generate
142 # a WCS from what's left
143 with self.assertRaises(lsst.pex.exceptions.TypeError):
144 readLsstSkyWcs(metadata, strip=False)
146 # try a full WCS with just CRPIX1 or 2 missing
147 for i in (1, 2):
148 metadata = self.makeMetadata()
149 metadata.remove(f"CRPIX{i}")
150 with self.assertRaises(lsst.pex.exceptions.TypeError):
151 readLsstSkyWcs(metadata, strip=False)
153 def testReadLsstSkyWcsNormalizeFk5(self):
154 """Test that readLsstSkyWcs correctly normalizes FK5 1975 to ICRS
155 """
156 equinox = 1975
157 metadata = self.makeMetadata()
158 metadata.set("RADESYS", "FK5")
159 metadata.set("EQUINOX", equinox)
160 crpix = self.getCrpix(metadata)
161 # record the original CRVAL before reading and stripping metadata
162 crvalFk5Deg = (metadata.getScalar("CRVAL1"), metadata.getScalar("CRVAL2"))
164 # read frameSet and compute crval
165 frameSet = readLsstSkyWcs(metadata, strip=True)
166 crvalRadians = frameSet.applyForward(crpix)
167 crvalPoint = SpherePoint(*[crvalRadians[i]*radians for i in range(2)])
169 # compare crval to the value recorded as the skyRef property of the current frame
170 skyFrame = frameSet.getFrame(ast.FrameSet.CURRENT)
171 recordedCrvalRadians = skyFrame.getSkyRef()
172 recordedCrvalPoint = SpherePoint(*[recordedCrvalRadians[i]*radians for i in range(2)])
173 self.assertSpherePointsAlmostEqual(crvalPoint, recordedCrvalPoint)
175 # get predicted crval by converting with astropy
176 crvalFk5 = SkyCoord(crvalFk5Deg[0], crvalFk5Deg[1], frame="fk5",
177 equinox=f"J{equinox}", unit="deg")
178 predictedCrvalIcrs = crvalFk5.icrs
179 predictedCrvalPoint = SpherePoint(predictedCrvalIcrs.ra.radian*radians,
180 predictedCrvalIcrs.dec.radian*radians)
181 # AST and astropy disagree by 0.025 arcsec; it's not worth worrying about because
182 # we will always use ICRS internally and almost always fit our own WCS.
183 self.assertSpherePointsAlmostEqual(crvalPoint, predictedCrvalPoint, maxSep=0.05*arcseconds)
185 def testReadLsstSkyWcsNormalizeRaDec(self):
186 """Test that a Dec, RA WCS frame set is normalized to RA, Dec
187 """
188 metadata = self.makeMetadata()
190 crpix = self.getCrpix(metadata)
192 # swap RA, Decaxes in metadata
193 crvalIn = SpherePoint(metadata.getScalar("CRVAL1"), metadata.getScalar("CRVAL2"), degrees)
194 metadata.set("CRVAL1", crvalIn[1].asDegrees())
195 metadata.set("CRVAL2", crvalIn[0].asDegrees())
196 metadata.set("CTYPE1", "DEC--TAN")
197 metadata.set("CTYPE2", "RA---TAN")
199 # create the wcs
200 frameSet = readLsstSkyWcs(metadata)
202 # compute pixel origin and compare to input crval
203 computedCrvalRadians = frameSet.applyForward(crpix)
204 computedCrval = SpherePoint(*[computedCrvalRadians[i]*radians for i in range(2)])
205 self.assertSpherePointsAlmostEqual(crvalIn, computedCrval)
207 def testGetPropertyListFromFitsChanWithComments(self):
208 fc = ast.FitsChan(ast.StringStream())
209 self.assertEqual(fc.className, "FitsChan")
211 # add one card for each supported type, with a comment
212 continueVal = "This is a continue card"
213 floatVal = 1.5
214 intVal = 99
215 logicalVal = True
216 stringVal = "This is a string"
217 fc.setFitsCN("ACONT", continueVal, "Comment for ACONT")
218 fc.setFitsF("AFLOAT", floatVal, "Comment for AFLOAT")
219 fc.setFitsI("ANINT", intVal, "Comment for ANINT")
220 fc.setFitsL("ALOGICAL", logicalVal, "Comment for ALOGICAL")
221 fc.setFitsS("ASTRING", stringVal, "Comment for ASTRING")
222 fc.setFitsCM("a comment, which will be ignored by getPropertyListFromFitsChan")
223 fc.setFitsU("ANUNDEF", "Comment for ANUNDEF")
224 expectedNames = ["ACONT", "AFLOAT", "ANINT", "ALOGICAL", "ASTRING", "ANUNDEF"]
226 self.assertEqual(fc.nCard, 7)
227 metadata = getPropertyListFromFitsChan(fc)
228 self.assertEqual(metadata.getOrderedNames(), expectedNames)
230 self.assertEqual(metadata.getScalar("ACONT"), continueVal)
231 self.assertAlmostEqual(metadata.getScalar("AFLOAT"), floatVal)
232 self.assertEqual(metadata.getScalar("ANINT"), intVal)
233 self.assertEqual(metadata.getScalar("ALOGICAL"), logicalVal)
234 self.assertEqual(metadata.getScalar("ASTRING"), stringVal)
235 self.assertEqual(metadata.getScalar("ACONT"), continueVal)
236 self.assertIsNone(metadata.getScalar("ANUNDEF"))
238 for name in expectedNames:
239 self.assertEqual(metadata.getComment(name), f"Comment for {name}")
241 # complex values are not supported by PropertyList
242 fc.setFitsCF("UNSUPP", complex(1, 0))
243 badCard = fc.getCard()
244 with self.assertRaises(lsst.pex.exceptions.TypeError):
245 getPropertyListFromFitsChan(fc)
246 fc.setCard(badCard)
247 fc.clearCard()
248 fc.findFits("UNSUPP", inc=False)
249 fc.delFits()
250 metadata = getPropertyListFromFitsChan(fc)
251 self.assertEqual(metadata.getOrderedNames(), expectedNames)
253 def testGetFitsCardsNoComments(self):
254 fc = ast.FitsChan(ast.StringStream())
255 self.assertEqual(fc.className, "FitsChan")
257 # add one card for each supported type, with a comment
258 continueVal = "This is a continue card"
259 floatVal = 1.5
260 intVal = 99
261 logicalVal = True
262 stringVal = "This is a string"
263 fc.setFitsCN("ACONT", continueVal)
264 fc.setFitsF("AFLOAT", floatVal)
265 fc.setFitsI("ANINT", intVal)
266 fc.setFitsL("ALOGICAL", logicalVal)
267 fc.setFitsS("ASTRING", stringVal)
268 fc.setFitsCM("a comment, which will be ignored by getPropertyListFromFitsChan")
270 self.assertEqual(fc.nCard, 6)
271 metadata = getPropertyListFromFitsChan(fc)
272 self.assertEqual(metadata.getOrderedNames(), ["ACONT", "AFLOAT", "ANINT", "ALOGICAL", "ASTRING"])
274 self.assertEqual(metadata.getScalar("ACONT"), continueVal)
275 self.assertAlmostEqual(metadata.getScalar("AFLOAT"), floatVal)
276 self.assertEqual(metadata.getScalar("ANINT"), intVal)
277 self.assertEqual(metadata.getScalar("ALOGICAL"), logicalVal)
278 self.assertEqual(metadata.getScalar("ASTRING"), stringVal)
279 self.assertEqual(metadata.getScalar("ACONT"), continueVal)
281 def testGetPropertyListFromFitsChanUnsupportedTypes(self):
282 fc = ast.FitsChan(ast.StringStream())
283 self.assertEqual(fc.className, "FitsChan")
284 fc.setFitsCF("ACOMPLEX", complex(1, 1))
285 with self.assertRaises(lsst.pex.exceptions.TypeError):
286 getPropertyListFromFitsChan(fc)
288 def testMakeSimpleWcsMetadata(self):
289 crpix = Point2D(111.1, 222.2)
290 crval = SpherePoint(45.6 * degrees, 12.3 * degrees)
291 scale = 1 * arcseconds
292 for orientation in (0 * degrees, 21 * degrees):
293 cdMatrix = makeCdMatrix(scale=scale, orientation=orientation)
294 for projection in ("TAN", "STG"):
295 metadata = makeSimpleWcsMetadata(crpix=crpix, crval=crval,
296 cdMatrix=cdMatrix, projection=projection)
297 desiredLength = 11 if orientation == 0 * degrees else 13
298 self.assertEqual(len(metadata.names()), desiredLength)
299 self.assertEqual(metadata.getScalar("RADESYS"), "ICRS")
300 self.assertFalse(metadata.exists("EQUINOX"))
301 self.assertEqual(metadata.getScalar("CTYPE1"), "RA---" + projection)
302 self.assertEqual(metadata.getScalar("CTYPE2"), "DEC--" + projection)
303 for i in range(2):
304 self.assertAlmostEqual(metadata.getScalar(f"CRPIX{i + 1}"), crpix[i] + 1)
305 self.assertAlmostEqual(metadata.getScalar(f"CRVAL{i + 1}"), crval[i].asDegrees())
306 self.assertEqual(metadata.getScalar(f"CUNIT{i + 1}"), "deg")
307 for i in range(2):
308 for j in range(2):
309 name = f"CD{i + 1}_{j + 1}"
310 if cdMatrix[i, j] != 0:
311 self.assertAlmostEqual(metadata.getScalar(name), cdMatrix[i, j])
312 else:
313 self.assertFalse(metadata.exists(name))
316class TestMemory(lsst.utils.tests.MemoryTestCase):
317 pass
320def setup_module(module):
321 lsst.utils.tests.init()
324if __name__ == "__main__": 324 ↛ 325line 324 didn't jump to line 325, because the condition on line 324 was never true
325 lsst.utils.tests.init()
326 unittest.main()