Coverage for tests/test_trailedSources.py: 31%

Shortcuts 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

109 statements  

1# 

2# This file is part of meas_extensions_trailedSources. 

3# 

4# Developed for the LSST Data Management System. 

5# This product includes software developed by the LSST Project 

6# (http://www.lsst.org). 

7# See the COPYRIGHT file at the top-level directory of this distribution 

8# for details of code ownership. 

9# 

10# This program is free software: you can redistribute it and/or modify 

11# it under the terms of the GNU General Public License as published by 

12# the Free Software Foundation, either version 3 of the License, or 

13# (at your option) any later version. 

14# 

15# This program is distributed in the hope that it will be useful, 

16# but WITHOUT ANY WARRANTY; without even the implied warranty of 

17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

18# GNU General Public License for more details. 

19# 

20# You should have received a copy of the GNU General Public License 

21# along with this program. If not, see <http://www.gnu.org/licenses/>. 

22# 

23 

24import numpy as np 

25import unittest 

26import lsst.utils.tests 

27import lsst.meas.extensions.trailedSources 

28from lsst.meas.base.tests import AlgorithmTestCase 

29from lsst.utils.tests import classParameters 

30from lsst.geom import Angle 

31 

32# Trailed-source length, angle, and centroid. 

33rng = np.random.default_rng(432) 

34nTrails = 50 

35Ls = rng.uniform(2, 20, nTrails) 

36thetas = rng.uniform(0, 2*np.pi, nTrails) 

37xcs = rng.uniform(0, 100, nTrails) 

38ycs = rng.uniform(0, 100, nTrails) 

39 

40 

41class TrailedSource: 

42 """Holds a set of true trail parameters. 

43 """ 

44 

45 def __init__(self, instFlux, length, angle, xc, yc): 

46 self.instFlux = instFlux 

47 self.length = length 

48 self.angle = angle 

49 self.center = lsst.geom.Point2D(xc, yc) 

50 self.x0 = xc - length/2 * np.cos(angle) 

51 self.y0 = yc - length/2 * np.sin(angle) 

52 self.x1 = xc + length/2 * np.cos(angle) 

53 self.y1 = yc + length/2 * np.sin(angle) 

54 

55 

56# "Extend" meas.base.tests.TestDataset 

57class TrailedTestDataset(lsst.meas.base.tests.TestDataset): 

58 """A dataset for testing trailed source measurements. 

59 Given a `TrailedSource`, construct a record of the true values and an 

60 Exposure. 

61 """ 

62 

63 def __init__(self, bbox, threshold=10.0, exposure=None, **kwds): 

64 super().__init__(bbox, threshold, exposure, **kwds) 

65 

66 def addTrailedSource(self, trail): 

67 """Add a trailed source to the simulation. 

68 'Re-implemented' version of 

69 `lsst.meas.base.tests.TestDataset.addSource`. Numerically integrates a 

70 Gaussian PSF over a line to obtain am image of a trailed source. 

71 """ 

72 

73 record = self.catalog.addNew() 

74 record.set(self.keys["centroid"], trail.center) 

75 rng = np.random.default_rng(32) 

76 covariance = rng.normal(0, 0.1, 4).reshape(2, 2) 

77 covariance[0, 1] = covariance[1, 0] 

78 record.set(self.keys["centroid_sigma"], covariance.astype(np.float32)) 

79 record.set(self.keys["shape"], self.psfShape) 

80 record.set(self.keys["isStar"], False) 

81 

82 # Sum the psf at each 

83 numIter = int(5*trail.length) 

84 xp = np.linspace(trail.x0, trail.x1, num=numIter) 

85 yp = np.linspace(trail.y0, trail.y1, num=numIter) 

86 for (x, y) in zip(xp, yp): 

87 pt = lsst.geom.Point2D(x, y) 

88 im = self.drawGaussian(self.exposure.getBBox(), trail.instFlux, 

89 lsst.afw.geom.Ellipse(self.psfShape, pt)) 

90 self.exposure.getMaskedImage().getImage().getArray()[:, :] += im.getArray() 

91 

92 record.set(self.keys["instFlux"], self.exposure.getImage().array.sum()) 

93 self._installFootprint(record, self.exposure.getImage()) 

94 

95 return record, self.exposure.getImage() 

96 

97 

98# Following from meas_base/test_NaiveCentroid.py 

99# Taken from NaiveCentroidTestCase 

100@classParameters(length=Ls, theta=thetas, xc=xcs, yc=ycs) 

101class TrailedSourcesTestCase(AlgorithmTestCase, lsst.utils.tests.TestCase): 

102 

103 def setUp(self): 

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

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

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

107 self.dataset = TrailedTestDataset(self.bbox) 

108 

109 self.trail = TrailedSource(100000.0, self.length, self.theta, self.xc, self.yc) 

110 self.dataset.addTrailedSource(self.trail) 

111 

112 def tearDown(self): 

113 del self.center 

114 del self.bbox 

115 del self.trail 

116 del self.dataset 

117 

118 def makeTrailedSourceMeasurementTask(self, plugin=None, dependencies=(), 

119 config=None, schema=None, algMetadata=None): 

120 """Set up a measurement task for a trailed source plugin. 

121 """ 

122 

123 config = self.makeSingleFrameMeasurementConfig(plugin=plugin, 

124 dependencies=dependencies) 

125 

126 # Make sure the shape slot is base_SdssShape 

127 config.slots.shape = "base_SdssShape" 

128 return self.makeSingleFrameMeasurementTask(plugin=plugin, 

129 dependencies=dependencies, 

130 config=config, schema=schema, 

131 algMetadata=algMetadata) 

132 

133 def testNaivePlugin(self): 

134 """Test the NaivePlugin measurements. 

135 Given a `TrailedTestDataset`, run the NaivePlugin measurement and 

136 compare the measured parameters to the true values. 

137 """ 

138 

139 # Set up and run Naive measurement. 

140 task = self.makeTrailedSourceMeasurementTask( 

141 plugin="ext_trailedSources_Naive", 

142 dependencies=("base_SdssCentroid", "base_SdssShape") 

143 ) 

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

145 task.run(catalog, exposure) 

146 record = catalog[0] 

147 

148 # Check the RA and Dec measurements 

149 wcs = exposure.getWcs() 

150 spt = wcs.pixelToSky(self.center) 

151 ra_true = spt.getRa().asDegrees() 

152 dec_true = spt.getDec().asDegrees() 

153 ra_meas = record.get("ext_trailedSources_Naive_ra") 

154 dec_meas = record.get("ext_trailedSources_Naive_dec") 

155 self.assertFloatsAlmostEqual(ra_true, ra_meas, atol=None, rtol=0.01) 

156 self.assertFloatsAlmostEqual(dec_true, dec_meas, atol=None, rtol=0.01) 

157 

158 # Check that root finder converged 

159 converged = record.get("ext_trailedSources_Naive_flag_noConverge") 

160 self.assertFalse(converged) 

161 

162 # Compare true with measured length and angle. 

163 # Accuracy is dependent on the second-moments measurements, so the 

164 # rtol values are simply rough upper bounds. 

165 length = record.get("ext_trailedSources_Naive_length") 

166 theta = record.get("ext_trailedSources_Naive_angle") 

167 self.assertFloatsAlmostEqual(length, self.trail.length, atol=None, rtol=0.1) 

168 self.assertAnglesAlmostEqual( 

169 Angle(theta % np.pi), Angle(self.trail.angle % np.pi), 

170 maxDiff=0.05*Angle(self.trail.angle % np.pi).wrapCtr() 

171 ) 

172 

173 # Check test setup 

174 self.assertNotEqual(length, self.trail.length) 

175 self.assertNotEqual(theta, self.trail.angle) 

176 

177 # Make sure measurement flag is False 

178 self.assertFalse(record.get("ext_trailedSources_Naive_flag")) 

179 

180 def testVeresPlugin(self): 

181 """Test the VeresPlugin measurements. 

182 Given a `TrailedTestDataset`, run the VeresPlugin measurement and 

183 compare the measured parameters to the true values. 

184 """ 

185 

186 # Set up and run Veres measurement. 

187 task = self.makeTrailedSourceMeasurementTask( 

188 plugin="ext_trailedSources_Veres", 

189 dependencies=("base_SdssShape", "ext_trailedSources_Naive") 

190 ) 

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

192 task.config.plugins['ext_trailedSources_Veres'].optimizerMethod = 'Powell' 

193 task.run(catalog, exposure) 

194 record = catalog[0] 

195 

196 # Make sure optmizer converged 

197 converged = record.get("ext_trailedSources_Veres_flag_nonConvergence") 

198 self.assertFalse(converged) 

199 

200 # Compare measured trail length and angle to true values 

201 # These measurements should perform at least as well as NaivePlugin 

202 length = record.get("ext_trailedSources_Veres_length") 

203 theta = record.get("ext_trailedSources_Veres_angle") 

204 self.assertFloatsAlmostEqual(length, self.trail.length, atol=None, rtol=0.1) 

205 self.assertAnglesAlmostEqual( 

206 Angle(theta % np.pi), Angle(self.trail.angle % np.pi), 

207 maxDiff=0.05*Angle(self.trail.angle % np.pi).wrapCtr() 

208 ) 

209 

210 # Make sure test setup is working as expected 

211 self.assertNotEqual(length, self.trail.length) 

212 self.assertNotEqual(theta, self.trail.angle) 

213 

214 # Test that reduced chi-squared is reasonable 

215 rChiSq = record.get("ext_trailedSources_Veres_rChiSq") 

216 self.assertGreater(rChiSq, 0.9) 

217 self.assertLess(rChiSq, 1.1) 

218 

219 # Make sure measurement flag is False 

220 self.assertFalse(record.get("ext_trailedSources_Veres_flag")) 

221 

222 

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

224 pass 

225 

226 

227def setup_module(module): 

228 lsst.utils.tests.init() 

229 

230 

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

232 lsst.utils.tests.init() 

233 unittest.main()