Coverage for tests/test_sdssShapePsf.py: 23%
103 statements
« prev ^ index » next coverage.py v6.4.2, created at 2022-08-01 01:39 -0700
« prev ^ index » next coverage.py v6.4.2, created at 2022-08-01 01:39 -0700
1#
2# LSST Data Management System
3# Copyright 2008-2017 AURA/LSST.
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 <https://www.lsstcorp.org/LegalNotices/>.
21#
23import unittest
25import numpy as np
27import lsst.geom
28import lsst.afw.geom as afwGeom
29import lsst.afw.image as afwImage
30import lsst.afw.math as afwMath
31import lsst.meas.algorithms as measAlg
32import lsst.meas.base as measBase
33import lsst.meas.base.tests as measBaseTests
34import lsst.utils.tests
37class SdssShapePsfTestCase(measBaseTests.AlgorithmTestCase, lsst.utils.tests.TestCase):
38 """Test case to ensure base_SdssShape_psf is being measured at source position
40 Note: this test lives here in meas_algorithms rather than meas_base (where SdssShape
41 lives) due to the need to apply a spatially varying PSF model such that the PSF is
42 different at each position in the image. This varying PSF model is built with
43 meas_algorithms' PcaPsf (which is not accessible from meas_base).
44 """
45 def setUp(self):
46 self.bbox = lsst.geom.Box2I(lsst.geom.Point2I(-20, -30), lsst.geom.Extent2I(240, 160))
47 self.dataset = measBaseTests.TestDataset(self.bbox)
48 # first two sources are points
49 self.pointCentroid1 = lsst.geom.Point2D(50.1, 49.8)
50 self.pointCentroid2 = lsst.geom.Point2D(-11.6, -1.7)
51 self.dataset.addSource(instFlux=1E5, centroid=self.pointCentroid1)
52 self.dataset.addSource(instFlux=2E5, centroid=self.pointCentroid2)
53 # third source is extended
54 self.extendedCentroid = lsst.geom.Point2D(149.9, 50.3)
55 self.dataset.addSource(instFlux=1E5, centroid=self.extendedCentroid,
56 shape=afwGeom.Quadrupole(8, 9, 3))
57 self.config = self.makeSingleFrameMeasurementConfig("base_SdssShape")
59 def tearDown(self):
60 del self.bbox
61 del self.dataset
62 del self.pointCentroid1
63 del self.pointCentroid2
64 del self.extendedCentroid
65 del self.config
67 def _computeVaryingPsf(self):
68 """Compute a varying PSF as a linear combination of PCA (== Karhunen-Loeve) basis functions
70 We simply desire a PSF that is not constant across the image, so the precise choice of
71 parameters (e.g., sigmas, setSpatialParameters) are not crucial.
72 """
73 kernelSize = 31
74 sigma1 = 1.75
75 sigma2 = 2.0*sigma1
76 basisKernelList = []
77 for sigma in (sigma1, sigma2):
78 basisKernel = afwMath.AnalyticKernel(kernelSize, kernelSize,
79 afwMath.GaussianFunction2D(sigma, sigma))
80 basisImage = afwImage.ImageD(basisKernel.getDimensions())
81 basisKernel.computeImage(basisImage, True)
82 basisImage /= np.sum(basisImage.getArray())
83 if sigma == sigma1:
84 basisImage0 = basisImage
85 else:
86 basisImage -= basisImage0
87 basisKernelList.append(afwMath.FixedKernel(basisImage))
89 order = 1
90 spFunc = afwMath.PolynomialFunction2D(order)
91 exactKernel = afwMath.LinearCombinationKernel(basisKernelList, spFunc)
92 exactKernel.setSpatialParameters([[1.0, 0, 0], [0.0, 0.5E-2, 0.2E-2]])
93 exactPsf = measAlg.PcaPsf(exactKernel)
95 return exactPsf
97 def _runMeasurementTask(self, psf=None):
98 task = self.makeSingleFrameMeasurementTask("base_SdssShape", config=self.config)
99 exposure, catalog = self.dataset.realize(10.0, task.schema, randomSeed=1234)
100 if psf:
101 exposure.setPsf(psf)
102 task.run(catalog, exposure)
103 return exposure, catalog
105 def _checkPsfShape(self, result, psfResult, psfTruth):
106 self.assertFloatsAlmostEqual(psfResult.getIxx(), psfTruth.getIxx(), rtol=1E-4)
107 self.assertFloatsAlmostEqual(psfResult.getIyy(), psfTruth.getIyy(), rtol=1E-4)
108 self.assertFloatsAlmostEqual(psfResult.getIxy(), psfTruth.getIxy(), rtol=1E-4)
109 self.assertFalse(result.getFlag(measBase.SdssShapeAlgorithm.PSF_SHAPE_BAD.number))
111 def testMeasureGoodPsf(self):
112 """Test that we measure shapes and record the PSF shape correctly
114 To ensure this, apply a varying PSF to the image such that different positions
115 can be distinguished by their different PSF model shapes.
116 """
117 # Apply varying PSF model to the exposure
118 varyingPsf = self._computeVaryingPsf()
119 exposure, catalog = self._runMeasurementTask(psf=varyingPsf)
120 key = measBase.SdssShapeResultKey(catalog.schema["base_SdssShape"])
121 # First make sure we did indeed get a varying PSF model across the exposure
122 psf = exposure.getPsf()
123 # Compare truth PSF at positions of two point sources
124 self.assertFloatsNotEqual(psf.computeShape(self.pointCentroid1).getIxx(),
125 psf.computeShape(self.pointCentroid2).getIxx(), rtol=1E-1)
126 self.assertFloatsNotEqual(psf.computeShape(self.pointCentroid1).getIyy(),
127 psf.computeShape(self.pointCentroid2).getIyy(), rtol=1E-1)
128 # Compare truth PSF at average position vs. truth PSF at extended source position
129 self.assertFloatsNotEqual(psf.computeShape(self.extendedCentroid).getIxx(),
130 psf.computeShape().getIxx(), rtol=1E-1)
131 self.assertFloatsNotEqual(psf.computeShape(self.extendedCentroid).getIyy(),
132 psf.computeShape().getIyy(), rtol=1E-1)
133 # Now check the base_SdssShape_psf entries against the PSF truth values
134 for record in catalog:
135 psfTruth = psf.computeShape(lsst.geom.Point2D(record.getX(), record.getY()))
136 result = record.get(key)
137 psfResult = key.getPsfShape(record)
138 self._checkPsfShape(result, psfResult, psfTruth)
140 def testResizedPcaPsf(self):
141 """Test that PcaPsf can resize itself.
143 This test resides here because PcaPsfs do not have their own test module"""
144 psf = self._computeVaryingPsf()
145 dim = psf.computeBBox().getDimensions()
146 for pad in [0, 4, -2]:
147 resizedPsf = psf.resized(dim.getX() + pad, dim.getY() + pad)
148 self.assertEqual(resizedPsf.computeBBox().getDimensions(),
149 lsst.geom.Extent2I(dim.getX() + pad, dim.getY() + pad))
150 if psf.getKernel().isSpatiallyVarying():
151 self.assertEqual(resizedPsf.getKernel().getSpatialParameters(),
152 psf.getKernel().getSpatialParameters())
153 else:
154 self.assertEqual(resizedPsf.getKernel().getKernelParameters(),
155 psf.getKernel().getKernelParameters())
156 self._compareKernelImages(resizedPsf, psf)
158 def _compareKernelImages(self, psf1, psf2):
159 """Test that overlapping portions of kernel images are identical
160 """
161 # warning: computeKernelImage modifies kernel parameters if spatially varying
162 im1 = psf1.computeKernelImage(psf1.getAveragePosition())
163 im2 = psf2.computeKernelImage(psf2.getAveragePosition())
164 bboxIntersection = im1.getBBox()
165 bboxIntersection.clip(im2.getBBox())
166 im1Intersection = afwImage.ImageD(im1, bboxIntersection)
167 im2Intersection = afwImage.ImageD(im2, bboxIntersection)
168 scale1 = im1.getArray().sum() / im1Intersection.getArray().sum()
169 scale2 = im2.getArray().sum() / im2Intersection.getArray().sum()
170 im1Arr = scale1 * im1Intersection.getArray()
171 im2Arr = scale2 * im2Intersection.getArray()
172 self.assertTrue(np.allclose(im1Arr, im2Arr),
173 "kernel images %s, %s do not match" % (im1Arr, im2Arr))
176class TestMemory(lsst.utils.tests.MemoryTestCase):
177 pass
180def setup_module(module):
181 lsst.utils.tests.init()
184if __name__ == "__main__": 184 ↛ 185line 184 didn't jump to line 185, because the condition on line 184 was never true
185 lsst.utils.tests.init()
186 unittest.main()