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# 

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 that root finder converged 

149 converged = record.get("ext_trailedSources_Naive_flag_noConverge") 

150 self.assertFalse(converged) 

151 

152 # Compare true with measured length and angle. 

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

154 # rtol values are simply rough upper bounds. 

155 length = record.get("ext_trailedSources_Naive_length") 

156 theta = record.get("ext_trailedSources_Naive_angle") 

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

158 self.assertAnglesAlmostEqual( 

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

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

161 ) 

162 

163 # Check test setup 

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

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

166 

167 # Make sure measurement flag is False 

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

169 

170 def testVeresPlugin(self): 

171 """Test the VeresPlugin measurements. 

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

173 compare the measured parameters to the true values. 

174 """ 

175 

176 # Set up and run Veres measurement. 

177 task = self.makeTrailedSourceMeasurementTask( 

178 plugin="ext_trailedSources_Veres", 

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

180 ) 

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

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

183 task.run(catalog, exposure) 

184 record = catalog[0] 

185 

186 # Make sure optmizer converged 

187 converged = record.get("ext_trailedSources_Veres_flag_nonConvergence") 

188 self.assertFalse(converged) 

189 

190 # Compare measured trail length and angle to true values 

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

192 length = record.get("ext_trailedSources_Veres_length") 

193 theta = record.get("ext_trailedSources_Veres_angle") 

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

195 self.assertAnglesAlmostEqual( 

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

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

198 ) 

199 

200 # Make sure test setup is working as expected 

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

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

203 

204 # Test that reduced chi-squared is reasonable 

205 rChiSq = record.get("ext_trailedSources_Veres_rChiSq") 

206 self.assertGreater(rChiSq, 0.9) 

207 self.assertLess(rChiSq, 1.1) 

208 

209 # Make sure measurement flag is False 

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

211 

212 

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

214 pass 

215 

216 

217def setup_module(module): 

218 lsst.utils.tests.init() 

219 

220 

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

222 lsst.utils.tests.init() 

223 unittest.main()