Coverage for tests/test_SdssCentroid.py: 20%

131 statements  

« prev     ^ index     » next       coverage.py v7.2.3, created at 2023-04-20 11:08 +0000

1# This file is part of meas_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 

24import numpy as np 

25 

26import lsst.geom 

27from lsst.meas.base.tests import (AlgorithmTestCase, CentroidTransformTestCase, 

28 SingleFramePluginTransformSetupHelper) 

29import lsst.afw.geom 

30import lsst.utils.tests 

31 

32# N.B. Some tests here depend on the noise realization in the test data 

33# or from the numpy random number generator. 

34# For the current test data and seed value, they pass, but they may not 

35# if the test data is regenerated or the seed value changes. I've marked 

36# these with an "rng dependent" comment. In most cases, they test that 

37# the measured instFlux lies within 2 sigma of the correct value, which we 

38# should expect to fail sometimes. 

39 

40 

41class SdssCentroidTestCase(AlgorithmTestCase, lsst.utils.tests.TestCase): 

42 

43 def setUp(self): 

44 self.center = lsst.geom.Point2D(50.1, 49.8) 

45 self.bbox = lsst.geom.Box2I(lsst.geom.Point2I(-20, -30), 

46 lsst.geom.Extent2I(140, 160)) 

47 self.dataset = lsst.meas.base.tests.TestDataset(self.bbox) 

48 self.dataset.addSource(100000.0, self.center) 

49 

50 def tearDown(self): 

51 

52 del self.center 

53 del self.bbox 

54 del self.dataset 

55 

56 def makeAlgorithm(self, ctrl=None): 

57 """Construct an algorithm and return both it and its schema. 

58 """ 

59 if ctrl is None: 

60 ctrl = lsst.meas.base.SdssCentroidControl() 

61 schema = lsst.meas.base.tests.TestDataset.makeMinimalSchema() 

62 algorithm = lsst.meas.base.SdssCentroidAlgorithm(ctrl, "base_SdssCentroid", schema) 

63 return algorithm, schema 

64 

65 def testSingleFramePlugin(self): 

66 """Test calling the algorithm through the plugin interface. 

67 """ 

68 task = self.makeSingleFrameMeasurementTask("base_SdssCentroid") 

69 exposure, catalog = self.dataset.realize(10.0, task.schema, randomSeed=0) 

70 task.run(catalog, exposure) 

71 record = catalog[0] 

72 self.assertFalse(record.get("base_SdssCentroid_flag")) 

73 self.assertFalse(record.get("base_SdssCentroid_flag_edge")) 

74 self.assertFloatsAlmostEqual(record.get("base_SdssCentroid_x"), record.get("truth_x"), rtol=0.005) 

75 self.assertFloatsAlmostEqual(record.get("base_SdssCentroid_y"), record.get("truth_y"), rtol=0.005) 

76 

77 def testMonteCarlo(self): 

78 """Test an ideal simulation, with no noise. 

79 

80 Demonstrate that: 

81 

82 - We get exactly the right answer, and 

83 - The reported uncertainty agrees with a Monte Carlo test of the noise. 

84 """ 

85 algorithm, schema = self.makeAlgorithm() 

86 exposure, catalog = self.dataset.realize(0.0, schema, randomSeed=1) 

87 record = catalog[0] 

88 x = record.get("truth_x") 

89 y = record.get("truth_y") 

90 instFlux = record.get("truth_instFlux") 

91 algorithm.measure(record, exposure) 

92 self.assertFloatsAlmostEqual(record.get("base_SdssCentroid_x"), x, rtol=1E-4) 

93 self.assertFloatsAlmostEqual(record.get("base_SdssCentroid_y"), y, rtol=1E-4) 

94 for noise in (0.001, 0.01): 

95 xList = [] 

96 yList = [] 

97 xErrList = [] 

98 yErrList = [] 

99 nSamples = 1000 

100 for repeat in range(nSamples): 

101 # By using ``repeat`` to seed the RNG, we get results which 

102 # fall within the tolerances defined below. If we allow this 

103 # test to be truly random, passing becomes RNG-dependent. 

104 exposure, catalog = self.dataset.realize(noise*instFlux, schema, randomSeed=repeat) 

105 record = catalog[0] 

106 algorithm.measure(record, exposure) 

107 xList.append(record.get("base_SdssCentroid_x")) 

108 yList.append(record.get("base_SdssCentroid_y")) 

109 xErrList.append(record.get("base_SdssCentroid_xErr")) 

110 yErrList.append(record.get("base_SdssCentroid_yErr")) 

111 xMean = np.mean(xList) 

112 yMean = np.mean(yList) 

113 xErrMean = np.mean(xErrList) 

114 yErrMean = np.mean(yErrList) 

115 xStandardDeviation = np.std(xList) 

116 yStandardDeviation = np.std(yList) 

117 self.assertFloatsAlmostEqual(xErrMean, xStandardDeviation, rtol=0.2) # rng dependent 

118 self.assertFloatsAlmostEqual(yErrMean, yStandardDeviation, rtol=0.2) # rng dependent 

119 self.assertLess(abs(xMean - x), 3.0*xErrMean / nSamples**0.5) # rng dependent 

120 self.assertLess(abs(yMean - y), 3.0*yErrMean / nSamples**0.5) # rng dependent 

121 

122 def testEdge(self): 

123 task = self.makeSingleFrameMeasurementTask("base_SdssCentroid") 

124 exposure, catalog = self.dataset.realize(10.0, task.schema, randomSeed=2) 

125 psfImage = exposure.getPsf().computeImage(self.center) 

126 # construct a box that won't fit the full PSF model 

127 bbox = psfImage.getBBox() 

128 bbox.grow(-5) 

129 subImage = lsst.afw.image.ExposureF(exposure, bbox) 

130 # we also need to install a smaller footprint, or NoiseReplacer 

131 # complains before we even get to measuring the centroid 

132 record = catalog[0] 

133 spanSet = lsst.afw.geom.SpanSet(bbox) 

134 newFootprint = lsst.afw.detection.Footprint(spanSet) 

135 peak = record.getFootprint().getPeaks()[0] 

136 newFootprint.addPeak(peak.getFx(), peak.getFy(), peak.getPeakValue()) 

137 record.setFootprint(newFootprint) 

138 # just measure the one object we've prepared for 

139 task.measure(catalog, subImage) 

140 self.assertTrue(record.get("base_SdssCentroid_flag")) 

141 self.assertTrue(record.get("base_SdssCentroid_flag_edge")) 

142 

143 def testNearEdge(self): 

144 task = self.makeSingleFrameMeasurementTask("base_SdssCentroid") 

145 exposure, catalog = self.dataset.realize(10.0, task.schema, randomSeed=2) 

146 psfImage = exposure.getPsf().computeImage(self.center) 

147 # construct a box that won't fit the full PSF model 

148 bbox = psfImage.getBBox() 

149 bbox.grow(-3) 

150 subImage = lsst.afw.image.ExposureF(exposure, bbox) 

151 # we also need to install a smaller footprint, or NoiseReplacer 

152 # complains before we even get to measuring the centroid 

153 record = catalog[0] 

154 spanSet = lsst.afw.geom.SpanSet(bbox) 

155 newFootprint = lsst.afw.detection.Footprint(spanSet) 

156 peak = record.getFootprint().getPeaks()[0] 

157 newFootprint.addPeak(peak.getFx(), peak.getFy(), peak.getPeakValue()) 

158 record.setFootprint(newFootprint) 

159 # just measure the one object we've prepared for 

160 task.measure(catalog, subImage) 

161 self.assertTrue(record.get("base_SdssCentroid_flag_near_edge")) 

162 self.assertTrue(record.get("base_SdssCentroid_flag")) 

163 

164 def testNo2ndDerivative(self): 

165 task = self.makeSingleFrameMeasurementTask("base_SdssCentroid") 

166 exposure, catalog = self.dataset.realize(10.0, task.schema, randomSeed=3) 

167 # cutout a subimage around object in the test image 

168 bbox = lsst.geom.Box2I(lsst.geom.Point2I(self.center), lsst.geom.Extent2I(1, 1)) 

169 bbox.grow(20) 

170 subImage = lsst.afw.image.ExposureF(exposure, bbox) 

171 # A completely flat image will trigger the no 2nd derivative error 

172 subImage.getMaskedImage().getImage().getArray()[:] = 0 

173 task.measure(catalog, subImage) 

174 self.assertTrue(catalog[0].get("base_SdssCentroid_flag")) 

175 self.assertTrue(catalog[0].get("base_SdssCentroid_flag_noSecondDerivative")) 

176 

177 def testNotAtMaximum(self): 

178 task = self.makeSingleFrameMeasurementTask("base_SdssCentroid") 

179 exposure, catalog = self.dataset.realize(10.0, task.schema, randomSeed=4) 

180 # cutout a subimage around the object in the test image 

181 bbox = lsst.geom.Box2I(lsst.geom.Point2I(self.center), lsst.geom.Extent2I(1, 1)) 

182 bbox.grow(20) 

183 subImage = lsst.afw.image.ExposureF(exposure, bbox) 

184 # zero out the central region, which will destroy the maximum 

185 subImage.getMaskedImage().getImage().getArray()[18:22, 18:22] = 0 

186 task.measure(catalog, subImage) 

187 self.assertTrue(catalog[0].get("base_SdssCentroid_flag")) 

188 self.assertTrue(catalog[0].get("base_SdssCentroid_flag_notAtMaximum")) 

189 

190 

191class SdssCentroidTransformTestCase(CentroidTransformTestCase, 

192 SingleFramePluginTransformSetupHelper, 

193 lsst.utils.tests.TestCase): 

194 controlClass = lsst.meas.base.SdssCentroidControl 

195 algorithmClass = lsst.meas.base.SdssCentroidAlgorithm 

196 transformClass = lsst.meas.base.SdssCentroidTransform 

197 flagNames = ('flag', 'flag_edge', 'flag_badData') 

198 singleFramePlugins = ('base_SdssCentroid',) 

199 forcedPlugins = ('base_SdssCentroid',) 

200 

201 

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

203 pass 

204 

205 

206def setup_module(module): 

207 lsst.utils.tests.init() 

208 

209 

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

211 lsst.utils.tests.init() 

212 unittest.main()