Coverage for tests/test_trailedSources.py: 30%

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

115 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 

30 

31import lsst.log 

32 

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

34rng = np.random.default_rng(432) 

35nTrails = 50 

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

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

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

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

40 

41 

42class TrailedSource: 

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

44 """ 

45 

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

47 self.instFlux = instFlux 

48 self.length = length 

49 self.angle = angle 

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

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

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

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

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

55 

56 

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

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

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

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

61 Exposure. 

62 """ 

63 

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

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

66 

67 def addTrailedSource(self, trail): 

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

69 'Re-implemented' version of 

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

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

72 """ 

73 

74 record = self.catalog.addNew() 

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

76 rng = np.random.default_rng(32) 

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

78 covariance[0, 1] = covariance[1, 0] 

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

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

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

82 

83 # Sum the psf at each 

84 numIter = int(10*trail.length) 

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

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

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

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

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

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

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

92 

93 totFlux = self.exposure.image.array.sum() 

94 self.exposure.image.array /= totFlux 

95 self.exposure.image.array *= trail.instFlux 

96 

97 record.set(self.keys["instFlux"], trail.instFlux) 

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

99 

100 return record, self.exposure.getImage() 

101 

102 

103# Following from meas_base/test_NaiveCentroid.py 

104# Taken from NaiveCentroidTestCase 

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

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

107 

108 def setUp(self): 

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

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

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

112 self.dataset = TrailedTestDataset(self.bbox) 

113 

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

115 self.dataset.addTrailedSource(self.trail) 

116 

117 def tearDown(self): 

118 del self.center 

119 del self.bbox 

120 del self.trail 

121 del self.dataset 

122 

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

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

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

126 """ 

127 

128 config = self.makeSingleFrameMeasurementConfig(plugin=plugin, 

129 dependencies=dependencies) 

130 

131 # Make sure the shape slot is base_SdssShape 

132 config.slots.shape = "base_SdssShape" 

133 return self.makeSingleFrameMeasurementTask(plugin=plugin, 

134 dependencies=dependencies, 

135 config=config, schema=schema, 

136 algMetadata=algMetadata) 

137 

138 def testNaivePlugin(self): 

139 """Test the NaivePlugin measurements. 

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

141 compare the measured parameters to the true values. 

142 """ 

143 

144 # Set up and run Naive measurement. 

145 task = self.makeTrailedSourceMeasurementTask( 

146 plugin="ext_trailedSources_Naive", 

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

148 ) 

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

150 task.run(catalog, exposure) 

151 record = catalog[0] 

152 

153 # Check the RA and Dec measurements 

154 wcs = exposure.getWcs() 

155 spt = wcs.pixelToSky(self.center) 

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

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

158 ra_meas = record.get("ext_trailedSources_Naive_ra") 

159 dec_meas = record.get("ext_trailedSources_Naive_dec") 

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

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

162 

163 # Check that root finder converged 

164 converged = record.get("ext_trailedSources_Naive_flag_noConverge") 

165 self.assertFalse(converged) 

166 

167 # Compare true with measured length, angle, and flux. 

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

169 # rtol values are simply rough upper bounds. 

170 length = record.get("ext_trailedSources_Naive_length") 

171 theta = record.get("ext_trailedSources_Naive_angle") 

172 flux = record.get("ext_trailedSources_Naive_flux") 

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

174 self.assertFloatsAlmostEqual(theta % np.pi, self.trail.angle % np.pi, 

175 atol=np.arctan(1/length), rtol=None) 

176 self.assertFloatsAlmostEqual(flux, self.trail.instFlux, atol=None, rtol=0.1) 

177 

178 # Check test setup 

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

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

181 

182 # Make sure measurement flag is False 

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

184 

185 def testVeresPlugin(self): 

186 """Test the VeresPlugin measurements. 

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

188 compare the measured parameters to the true values. 

189 """ 

190 

191 # Set up and run Veres measurement. 

192 task = self.makeTrailedSourceMeasurementTask( 

193 plugin="ext_trailedSources_Veres", 

194 dependencies=( 

195 "base_SdssCentroid", 

196 "base_SdssShape", 

197 "ext_trailedSources_Naive") 

198 ) 

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

200 task.run(catalog, exposure) 

201 record = catalog[0] 

202 

203 # Make sure optmizer converged 

204 converged = record.get("ext_trailedSources_Veres_flag_nonConvergence") 

205 self.assertFalse(converged) 

206 

207 # Compare measured trail length, angle, and flux to true values 

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

209 length = record.get("ext_trailedSources_Veres_length") 

210 theta = record.get("ext_trailedSources_Veres_angle") 

211 flux = record.get("ext_trailedSources_Veres_flux") 

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

213 self.assertFloatsAlmostEqual(theta % np.pi, self.trail.angle % np.pi, 

214 atol=np.arctan(1/length), rtol=None) 

215 self.assertFloatsAlmostEqual(flux, self.trail.instFlux, atol=None, rtol=0.1) 

216 

217 # Make sure test setup is working as expected 

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

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

220 

221 # Test that reduced chi-squared is reasonable 

222 rChiSq = record.get("ext_trailedSources_Veres_rChiSq") 

223 self.assertGreater(rChiSq, 0.8) 

224 self.assertLess(rChiSq, 1.3) 

225 

226 # Make sure measurement flag is False 

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

228 

229 

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

231 pass 

232 

233 

234def setup_module(module): 

235 lsst.utils.tests.init() 

236 

237 

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

239 lsst.utils.tests.init() 

240 unittest.main()