Hide keyboard shortcuts

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

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 

27import lsst.afw.image 

28import lsst.afw.table 

29import lsst.utils.tests 

30 

31from lsst.meas.base.tests import (AlgorithmTestCase, FluxTransformTestCase, 

32 SingleFramePluginTransformSetupHelper) 

33 

34 

35class PsfFluxTestCase(AlgorithmTestCase, lsst.utils.tests.TestCase): 

36 

37 def setUp(self): 

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

39 self.bbox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0), 

40 lsst.geom.Extent2I(100, 100)) 

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

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

43 

44 def tearDown(self): 

45 del self.center 

46 del self.bbox 

47 del self.dataset 

48 

49 def makeAlgorithm(self, ctrl=None): 

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

51 """ 

52 if ctrl is None: 

53 ctrl = lsst.meas.base.PsfFluxControl() 

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

55 algorithm = lsst.meas.base.PsfFluxAlgorithm(ctrl, "base_PsfFlux", schema) 

56 return algorithm, schema 

57 

58 def testMasking(self): 

59 algorithm, schema = self.makeAlgorithm() 

60 # Results are RNG dependent; we choose a seed that is known to pass. 

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

62 record = catalog[0] 

63 badPoint = lsst.geom.Point2I(self.center) + lsst.geom.Extent2I(3, 4) 

64 imageArray = exposure.getMaskedImage().getImage().getArray() 

65 maskArray = exposure.getMaskedImage().getMask().getArray() 

66 badMask = exposure.getMaskedImage().getMask().getPlaneBitMask("BAD") 

67 imageArray[badPoint.getY() - exposure.getY0(), badPoint.getX() - exposure.getX0()] = np.inf 

68 maskArray[badPoint.getY() - exposure.getY0(), badPoint.getX() - exposure.getX0()] |= badMask 

69 # Should get an infinite value exception, because we didn't mask that 

70 # one pixel 

71 with self.assertRaises(lsst.meas.base.PixelValueError): 

72 algorithm.measure(record, exposure) 

73 # If we do mask it, we should get a reasonable result 

74 ctrl = lsst.meas.base.PsfFluxControl() 

75 ctrl.badMaskPlanes = ["BAD"] 

76 algorithm, schema = self.makeAlgorithm(ctrl) 

77 algorithm.measure(record, exposure) 

78 self.assertFloatsAlmostEqual(record.get("base_PsfFlux_instFlux"), 

79 record.get("truth_instFlux"), 

80 atol=3*record.get("base_PsfFlux_instFluxErr")) 

81 # If we mask the whole image, we should get a MeasurementError 

82 maskArray[:, :] |= badMask 

83 with self.assertRaises(lsst.meas.base.MeasurementError) as context: 

84 algorithm.measure(record, exposure) 

85 self.assertEqual(context.exception.getFlagBit(), 

86 lsst.meas.base.PsfFluxAlgorithm.NO_GOOD_PIXELS.number) 

87 

88 def testSubImage(self): 

89 """Test measurement on sub-images. 

90 

91 Specifically, checks that we don't get confused by images with nonzero 

92 ``xy0``, and that the ``EDGE`` flag is set when it should be. 

93 """ 

94 

95 algorithm, schema = self.makeAlgorithm() 

96 # Results are RNG dependent; we choose a seed that is known to pass. 

97 exposure, catalog = self.dataset.realize(10.0, schema, randomSeed=1) 

98 record = catalog[0] 

99 psfImage = exposure.getPsf().computeImage(record.getCentroid()) 

100 bbox = psfImage.getBBox() 

101 bbox.grow(-1) 

102 subExposure = exposure.Factory(exposure, bbox, lsst.afw.image.LOCAL) 

103 algorithm.measure(record, subExposure) 

104 self.assertFloatsAlmostEqual(record.get("base_PsfFlux_instFlux"), record.get("truth_instFlux"), 

105 atol=3*record.get("base_PsfFlux_instFluxErr")) 

106 self.assertTrue(record.get("base_PsfFlux_flag_edge")) 

107 

108 def testNoPsf(self): 

109 """Test that we raise `FatalAlgorithmError` when there's no PSF. 

110 """ 

111 algorithm, schema = self.makeAlgorithm() 

112 # Results are RNG dependent; we choose a seed that is known to pass. 

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

114 exposure.setPsf(None) 

115 with self.assertRaises(lsst.meas.base.FatalAlgorithmError): 

116 algorithm.measure(catalog[0], exposure) 

117 

118 def testMonteCarlo(self): 

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

120 

121 Demonstrate that: 

122 

123 - We get exactly the right answer, and 

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

125 """ 

126 algorithm, schema = self.makeAlgorithm() 

127 # Results are RNG dependent; we choose a seed that is known to pass. 

128 exposure, catalog = self.dataset.realize(0.0, schema, randomSeed=3) 

129 record = catalog[0] 

130 instFlux = record.get("truth_instFlux") 

131 algorithm.measure(record, exposure) 

132 self.assertFloatsAlmostEqual(record.get("base_PsfFlux_instFlux"), instFlux, rtol=1E-3) 

133 self.assertFloatsAlmostEqual(record.get("base_PsfFlux_instFluxErr"), 0.0, rtol=1E-3) 

134 for noise in (0.001, 0.01, 0.1): 

135 instFluxes = [] 

136 instFluxErrs = [] 

137 nSamples = 1000 

138 for repeat in range(nSamples): 

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

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

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

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

143 record = catalog[0] 

144 algorithm.measure(record, exposure) 

145 instFluxes.append(record.get("base_PsfFlux_instFlux")) 

146 instFluxErrs.append(record.get("base_PsfFlux_instFluxErr")) 

147 instFluxMean = np.mean(instFluxes) 

148 instFluxErrMean = np.mean(instFluxErrs) 

149 instFluxStandardDeviation = np.std(instFluxes) 

150 self.assertFloatsAlmostEqual(instFluxErrMean, instFluxStandardDeviation, rtol=0.10) 

151 self.assertLess(instFluxMean - instFlux, 2.0*instFluxErrMean / nSamples**0.5) 

152 

153 def testSingleFramePlugin(self): 

154 task = self.makeSingleFrameMeasurementTask("base_PsfFlux") 

155 # Results are RNG dependent; we choose a seed that is known to pass. 

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

157 task.run(catalog, exposure) 

158 record = catalog[0] 

159 self.assertFalse(record.get("base_PsfFlux_flag")) 

160 self.assertFalse(record.get("base_PsfFlux_flag_noGoodPixels")) 

161 self.assertFalse(record.get("base_PsfFlux_flag_edge")) 

162 self.assertFloatsAlmostEqual(record.get("base_PsfFlux_instFlux"), record.get("truth_instFlux"), 

163 atol=3*record.get("base_PsfFlux_instFluxErr")) 

164 

165 def testForcedPlugin(self): 

166 task = self.makeForcedMeasurementTask("base_PsfFlux") 

167 # Results of this test are RNG dependent: we choose seeds that are 

168 # known to pass. 

169 measWcs = self.dataset.makePerturbedWcs(self.dataset.exposure.getWcs(), randomSeed=5) 

170 measDataset = self.dataset.transform(measWcs) 

171 exposure, truthCatalog = measDataset.realize(10.0, measDataset.makeMinimalSchema(), randomSeed=5) 

172 refCat = self.dataset.catalog 

173 refWcs = self.dataset.exposure.getWcs() 

174 measCat = task.generateMeasCat(exposure, refCat, refWcs) 

175 task.attachTransformedFootprints(measCat, refCat, exposure, refWcs) 

176 task.run(measCat, exposure, refCat, refWcs) 

177 measRecord = measCat[0] 

178 truthRecord = truthCatalog[0] 

179 # Centroid tolerances set to ~ single precision epsilon 

180 self.assertFloatsAlmostEqual(measRecord.get("slot_Centroid_x"), 

181 truthRecord.get("truth_x"), rtol=1E-7) 

182 self.assertFloatsAlmostEqual(measRecord.get("slot_Centroid_y"), 

183 truthRecord.get("truth_y"), rtol=1E-7) 

184 self.assertFalse(measRecord.get("base_PsfFlux_flag")) 

185 self.assertFalse(measRecord.get("base_PsfFlux_flag_noGoodPixels")) 

186 self.assertFalse(measRecord.get("base_PsfFlux_flag_edge")) 

187 self.assertFloatsAlmostEqual(measRecord.get("base_PsfFlux_instFlux"), 

188 truthCatalog.get("truth_instFlux"), rtol=1E-3) 

189 self.assertLess(measRecord.get("base_PsfFlux_instFluxErr"), 500.0) 

190 

191 

192class PsfFluxTransformTestCase(FluxTransformTestCase, SingleFramePluginTransformSetupHelper, 

193 lsst.utils.tests.TestCase): 

194 controlClass = lsst.meas.base.PsfFluxControl 

195 algorithmClass = lsst.meas.base.PsfFluxAlgorithm 

196 transformClass = lsst.meas.base.PsfFluxTransform 

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

198 singleFramePlugins = ('base_PsfFlux',) 

199 forcedPlugins = ('base_PsfFlux',) 

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