Coverage for tests/test_sdssShapePsf.py: 21%

103 statements  

« prev     ^ index     » next       coverage.py v7.4.0, created at 2024-01-23 11:18 +0000

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# 

22 

23import unittest 

24 

25import numpy as np 

26 

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 

35 

36 

37class SdssShapePsfTestCase(measBaseTests.AlgorithmTestCase, lsst.utils.tests.TestCase): 

38 """Test case to ensure base_SdssShape_psf is being measured at source position 

39 

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") 

58 

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 

66 

67 def _computeVaryingPsf(self): 

68 """Compute a varying PSF as a linear combination of PCA (== Karhunen-Loeve) basis functions 

69 

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)) 

88 

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) 

94 

95 return exactPsf 

96 

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 

104 

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)) 

110 

111 def testMeasureGoodPsf(self): 

112 """Test that we measure shapes and record the PSF shape correctly 

113 

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(psf.getAveragePosition()).getIxx(), rtol=1E-1) 

131 self.assertFloatsNotEqual(psf.computeShape(self.extendedCentroid).getIyy(), 

132 psf.computeShape(psf.getAveragePosition()).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) 

139 

140 def testResizedPcaPsf(self): 

141 """Test that PcaPsf can resize itself. 

142 

143 This test resides here because PcaPsfs do not have their own test module""" 

144 psf = self._computeVaryingPsf() 

145 dim = psf.computeBBox(psf.getAveragePosition()).getDimensions() 

146 for pad in [0, 4, -2]: 

147 resizedPsf = psf.resized(dim.getX() + pad, dim.getY() + pad) 

148 self.assertEqual( 

149 resizedPsf.computeBBox(resizedPsf.getAveragePosition()).getDimensions(), 

150 lsst.geom.Extent2I(dim.getX() + pad, dim.getY() + pad) 

151 ) 

152 if psf.getKernel().isSpatiallyVarying(): 

153 self.assertEqual(resizedPsf.getKernel().getSpatialParameters(), 

154 psf.getKernel().getSpatialParameters()) 

155 else: 

156 self.assertEqual(resizedPsf.getKernel().getKernelParameters(), 

157 psf.getKernel().getKernelParameters()) 

158 self._compareKernelImages(resizedPsf, psf) 

159 

160 def _compareKernelImages(self, psf1, psf2): 

161 """Test that overlapping portions of kernel images are identical 

162 """ 

163 # warning: computeKernelImage modifies kernel parameters if spatially varying 

164 im1 = psf1.computeKernelImage(psf1.getAveragePosition()) 

165 im2 = psf2.computeKernelImage(psf2.getAveragePosition()) 

166 bboxIntersection = im1.getBBox() 

167 bboxIntersection.clip(im2.getBBox()) 

168 im1Intersection = afwImage.ImageD(im1, bboxIntersection) 

169 im2Intersection = afwImage.ImageD(im2, bboxIntersection) 

170 scale1 = im1.getArray().sum() / im1Intersection.getArray().sum() 

171 scale2 = im2.getArray().sum() / im2Intersection.getArray().sum() 

172 im1Arr = scale1 * im1Intersection.getArray() 

173 im2Arr = scale2 * im2Intersection.getArray() 

174 self.assertTrue(np.allclose(im1Arr, im2Arr), 

175 "kernel images %s, %s do not match" % (im1Arr, im2Arr)) 

176 

177 

178class TestMemory(lsst.utils.tests.MemoryTestCase): 

179 pass 

180 

181 

182def setup_module(module): 

183 lsst.utils.tests.init() 

184 

185 

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

187 lsst.utils.tests.init() 

188 unittest.main()