Coverage for tests/test_tableUtils.py: 16%
119 statements
« prev ^ index » next coverage.py v7.5.0, created at 2024-05-01 15:48 -0700
« prev ^ index » next coverage.py v7.5.0, created at 2024-05-01 15:48 -0700
1# LSST Data Management System
2# Copyright 2016 LSST Corporation.
3#
4# This product includes software developed by the
5# LSST Project (http://www.lsst.org/).
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation, either version 3 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the LSST License Statement and
18# the GNU General Public License along with this program. If not,
19# see <http://www.lsstcorp.org/LegalNotices/>.
20#
21# The classes in this test are a little non-standard to reduce code
22# duplication and support automated unittest discovery.
23# A base class includes all the code that implements the testing and
24# itself inherits from unittest.TestCase. unittest automated discovery
25# will scan all classes that inherit from unittest.TestCase and invoke
26# any test methods found. To prevent this base class from being executed
27# the test methods are placed in a different class that does not inherit
28# from unittest.TestCase. The actual test classes then inherit from
29# both the testing class and the implementation class allowing test
30# discovery to only run tests found in the subclasses.
32import math
33import unittest
35import numpy as np
37import lsst.utils.tests
38import lsst.geom
39import lsst.afw.geom as afwGeom
40import lsst.afw.table as afwTable
43class UpdateTestCase(lsst.utils.tests.TestCase):
44 """A test case for the lsst.afw.table.updateRefCentroids and updateSourceCoords
45 """
47 def setUp(self):
48 self.crval = lsst.geom.SpherePoint(44.0, 45.0, lsst.geom.degrees)
49 self.crpix = lsst.geom.Point2D(15000, 4000)
51 arcsecPerPixel = 1/3600.0
52 cdMatrix = afwGeom.makeCdMatrix(arcsecPerPixel * lsst.geom.arcseconds)
53 self.wcs = afwGeom.makeSkyWcs(crval=self.crval, crpix=self.crpix, cdMatrix=cdMatrix)
55 refSchema = afwTable.SimpleTable.makeMinimalSchema()
56 self.refCentroidKey = afwTable.Point2DKey.addFields(
57 refSchema, "centroid", "centroid", "pixels")
58 self.refCoordKey = afwTable.CoordKey(refSchema["coord"])
59 self.refHasCentroidKey = refSchema.addField("hasCentroid", type="Flag")
60 self.refCat = afwTable.SimpleCatalog(refSchema)
62 # an alias is required to make src.getCentroid() work;
63 # simply defining a field named "slot_Centroid" doesn't suffice
64 srcSchema = afwTable.SourceTable.makeMinimalSchema()
65 self.srcCentroidKey = afwTable.Point2DKey.addFields(srcSchema, "base_SdssCentroid",
66 "centroid", "pixels")
67 self.srcCentroidErrKey = afwTable.CovarianceMatrix2fKey.addFields(srcSchema, "base_SdssCentroid",
68 ["x", "y"], "pixels")
69 self.srcCoordErrKey = afwTable.CoordKey.addErrorFields(srcSchema)
71 srcAliases = srcSchema.getAliasMap()
72 srcAliases.set("slot_Centroid", "base_SdssCentroid")
73 self.srcCoordKey = afwTable.CoordKey(srcSchema["coord"])
74 self.sourceCat = afwTable.SourceCatalog(srcSchema)
76 def tearDown(self):
77 del self.wcs
78 del self.refCat
79 del self.sourceCat
81 def testNull(self):
82 """Check that an empty list causes no problems for either function"""
83 afwTable.updateRefCentroids(self.wcs, [])
84 afwTable.updateSourceCoords(self.wcs, [])
86 def testRefCenter(self):
87 """Check that a ref obj at the center is handled as expected"""
88 refObj = self.refCat.addNew()
89 refObj.set(self.refCoordKey, self.crval)
91 # initial centroid should be nan and hasCentroid False
92 nanRefCentroid = self.refCat[0].get(self.refCentroidKey)
93 for val in nanRefCentroid:
94 self.assertTrue(math.isnan(val))
95 self.assertFalse(self.refCat[0].get(self.refHasCentroidKey))
97 # computed centroid should be crpix and hasCentroid True
98 afwTable.updateRefCentroids(self.wcs, self.refCat)
99 refCentroid = self.refCat[0].get(self.refCentroidKey)
100 self.assertPairsAlmostEqual(refCentroid, self.crpix)
101 self.assertTrue(self.refCat[0].get(self.refHasCentroidKey))
103 # coord should not be changed
104 self.assertEqual(self.refCat[0].get(self.refCoordKey), self.crval)
106 def testSourceCenter(self):
107 """Check that a source at the center is handled as expected"""
108 src = self.sourceCat.addNew()
109 src.set(self.srcCentroidKey, self.crpix)
111 # initial coord should be nan; as a sanity-check
112 nanSourceCoord = self.sourceCat[0].get(self.srcCoordKey)
113 for val in nanSourceCoord:
114 self.assertTrue(math.isnan(val))
116 # compute coord should be crval
117 afwTable.updateSourceCoords(self.wcs, self.sourceCat)
118 srcCoord = self.sourceCat[0].get(self.srcCoordKey)
119 self.assertPairsAlmostEqual(srcCoord, self.crval)
121 # centroid should not be changed; also make sure that getCentroid words
122 self.assertEqual(self.sourceCat[0].getCentroid(), self.crpix)
124 def testLists(self):
125 """Check updating lists of reference objects and sources"""
126 # arbitrary but reasonable values that are intentionally different than
127 # testCatalogs
128 maxPix = 1000
129 numPoints = 10
130 self.setCatalogs(maxPix=maxPix, numPoints=numPoints)
132 # update the catalogs as lists
133 afwTable.updateSourceCoords(self.wcs, [s for s in self.sourceCat])
134 afwTable.updateRefCentroids(self.wcs, [r for r in self.refCat])
136 self.checkCatalogs()
138 def testCatalogs(self):
139 """Check updating catalogs of reference objects and sources"""
140 # arbitrary but reasonable values that are intentionally different than
141 # testLists
142 maxPix = 2000
143 numPoints = 9
144 self.setCatalogs(maxPix=maxPix, numPoints=numPoints)
146 # update the catalogs
147 afwTable.updateSourceCoords(self.wcs, self.sourceCat)
148 afwTable.updateRefCentroids(self.wcs, self.refCat)
150 # check that centroids and coords match
151 self.checkCatalogs()
153 def testCoordErrors(self):
154 """Check that updateSourceCoords has correctly propagated the centroid
155 errors.
156 """
157 maxPix = 2000
158 numPoints = 10
160 self.setCatalogs(maxPix=maxPix, numPoints=numPoints)
161 scale = (1.0 * lsst.geom.arcseconds).asDegrees()
162 # update the catalogs
163 afwTable.updateSourceCoords(self.wcs, self.sourceCat)
164 for src in self.sourceCat:
165 center = src.get(self.srcCentroidKey)
166 skyCenter = self.wcs.pixelToSky(center)
167 localGnomonicWcs = lsst.afw.geom.makeSkyWcs(
168 center, skyCenter, np.diag((scale, scale)))
169 measurementToLocalGnomonic = self.wcs.getTransform().then(
170 localGnomonicWcs.getTransform().inverted()
171 )
172 localMatrix = measurementToLocalGnomonic.getJacobian(center)
173 radMatrix = np.radians(localMatrix / 3600)
175 centroidErr = src.get(self.srcCentroidErrKey)
176 coordErr = radMatrix.dot(centroidErr.dot(radMatrix.T))
177 catCoordErr = src.get(self.srcCoordErrKey)
178 np.testing.assert_almost_equal(coordErr, catCoordErr)
180 def checkCatalogs(self, maxPixDiff=1e-5, maxSkyDiff=0.001*lsst.geom.arcseconds):
181 """Check that the source and reference object catalogs have equal centroids and coords"""
182 self.assertEqual(len(self.sourceCat), len(self.refCat))
184 for src, refObj in zip(self.sourceCat, self.refCat):
185 self.assertTrue(refObj.get(self.refHasCentroidKey))
186 srcCentroid = src.get(self.srcCentroidKey)
187 refCentroid = refObj.get(self.refCentroidKey)
188 self.assertPairsAlmostEqual(
189 srcCentroid, refCentroid, maxDiff=maxPixDiff)
191 srcCoord = src.get(self.srcCoordKey)
192 refCoord = refObj.get(self.refCoordKey)
193 self.assertSpherePointsAlmostEqual(
194 srcCoord, refCoord, maxSep=maxSkyDiff)
196 def setCatalogs(self, maxPix, numPoints):
197 """Set the source centroids and reference object coords
199 Set self.sourceCat centroids to a square grid of points
200 and set self.refCat coords to the corresponding sky positions
202 The catalogs must be empty to start
204 @param[in] maxPix maximum pixel position; used for both x and y;
205 the min is the negative of maxPix
206 @param[in] numPoints number of points in x or y; total points = numPoints*numPoints
207 """
208 if len(self.sourceCat) != 0:
209 raise RuntimeError("self.sourceCat must be empty")
210 if len(self.refCat) != 0:
211 raise RuntimeError("self.refCat must be empty")
213 for i in np.linspace(-maxPix, maxPix, numPoints):
214 for j in np.linspace(-maxPix, maxPix, numPoints):
215 centroid = lsst.geom.Point2D(i, j)
216 centroidErr = np.array([[0.05, 0], [0, 0.05]])
217 src = self.sourceCat.addNew()
218 src.set(self.srcCentroidKey, centroid)
219 src.set(self.srcCentroidErrKey, centroidErr)
221 refObj = self.refCat.addNew()
222 coord = self.wcs.pixelToSky(centroid)
223 refObj.set(self.refCoordKey, coord)
226class MemoryTester(lsst.utils.tests.MemoryTestCase):
227 pass
230def setup_module(module):
231 lsst.utils.tests.init()
234if __name__ == "__main__": 234 ↛ 235line 234 didn't jump to line 235, because the condition on line 234 was never true
235 lsst.utils.tests.init()
236 unittest.main()