Coverage for tests/test_transmissionCurve.py: 13%

228 statements  

« prev     ^ index     » next       coverage.py v6.4, created at 2022-06-02 03:42 -0700

1# 

2# LSST Data Management System 

3# Copyright 2017 LSST/AURA. 

4# 

5# This product includes software developed by the 

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

7# 

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

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

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

11# (at your option) any later version. 

12# 

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

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

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

16# GNU General Public License for more details. 

17# 

18# You should have received a copy of the LSST License Statement and 

19# the GNU General Public License along with this program. If not, 

20# see <http://www.lsstcorp.org/LegalNotices/>. 

21# 

22 

23import unittest 

24 

25import numpy as np 

26 

27import lsst.utils.tests 

28import lsst.pex.exceptions 

29import lsst.geom 

30import lsst.afw.geom 

31import lsst.afw.image 

32import lsst.afw.table 

33 

34 

35def makeTestCurve(random, x0, x1, y0=0.0, y1=0.0): 

36 """Return a piecewise callable appropriate for testing 

37 TransmissionCurve objects. 

38 

39 The returned callable can be called with either scalar or numpy.ndarray 

40 arguments. 

41 

42 Between x0 and x1, the returned function is a nonnegative 4th-order 

43 polynomial. At and below x0 it is constant with value y0, and at and 

44 above x1 it is equal to y1. At exactly x0 and x1 its first derivative 

45 is zero. 

46 

47 y0 and y1 may be None to indicate a random value should be drawn 

48 (on the interval [0, 1]). 

49 """ 

50 if y0 is None: 

51 y0 = random.rand() 

52 if y1 is None: 

53 y1 = random.rand() 

54 assert y0 >= 0.0 

55 assert y1 >= 0.0 

56 alpha0 = np.abs(y0 - y1) 

57 if alpha0 == 0.0: 

58 alpha0 = 1.0 

59 mu = (x1 - x0)*(0.25 + 0.5*random.rand()) 

60 alpha = alpha0*(0.25 + 0.5*random.rand()) 

61 n = 5 

62 A = np.zeros([n, n], dtype=float) 

63 dx = x1 - x0 

64 A[0, 0] = 1.0 

65 A[1, :] = [dx**k for k in range(n)] 

66 A[2, 1] = 1.0 

67 A[3, :] = [k*dx**(k - 1) for k in range(n)] 

68 A[4, :] = [mu**k for k in range(n)] 

69 b = np.array([y0, y1, 0.0, 0.0, alpha], dtype=float) 

70 coeffs = np.linalg.solve(A, b) 

71 

72 def curve(x): 

73 result = sum(c*(x - x0)**k for k, c in enumerate(coeffs)) 

74 result[x <= x0] = y0 

75 result[x >= x1] = y1 

76 return result 

77 

78 return curve 

79 

80 

81class TransmissionCurveTestCase(lsst.utils.tests.TestCase): 

82 

83 def setUp(self): 

84 self.random = np.random.RandomState(1) 

85 self.points = [lsst.geom.Point2D(self.random.rand(), self.random.rand()) for i in range(5)] 

86 self.minWavelength = 5000 + self.random.rand() 

87 self.maxWavelength = 5500 + self.random.rand() 

88 

89 def randIfNone(self, v): 

90 """Return a random number if the given input is None, but pass it through if it is not.""" 

91 if v is None: 

92 return self.random.rand() 

93 return v 

94 

95 def checkEvaluation(self, tc, wavelengths, expected, rtol=0.0, atol=0.0): 

96 """Test that evaluating a TransmissionCurve on the given wavelengths array yields the given 

97 expected throughputs. 

98 """ 

99 for point in self.points: 

100 throughput = tc.sampleAt(point, wavelengths) 

101 self.assertFloatsAlmostEqual(throughput, expected, rtol=rtol, atol=atol) 

102 throughput2 = np.zeros(wavelengths.size, dtype=float) 

103 tc.sampleAt(point, wavelengths, out=throughput2) 

104 self.assertFloatsEqual(throughput2, throughput) 

105 

106 def assertTransmissionCurvesEqual(self, a, b, rtol=0.0, atol=0.0): 

107 """Test whether two TransimssionCurves are equivalent.""" 

108 self.assertEqual(a.getWavelengthBounds(), b.getWavelengthBounds()) 

109 self.assertEqual(a.getThroughputAtBounds(), b.getThroughputAtBounds()) 

110 wavelengths = np.linspace(*(a.getWavelengthBounds() + (100,))) 

111 for point in self.points: 

112 self.assertFloatsAlmostEqual( 

113 a.sampleAt(point, wavelengths), 

114 b.sampleAt(point, wavelengths), 

115 rtol=rtol, atol=atol 

116 ) 

117 

118 def checkPersistence(self, tc, points=None): 

119 """Test that a TransmissionCurve round-trips through persistence.""" 

120 if points is None: 

121 points = self.points 

122 with lsst.utils.tests.getTempFilePath(".fits") as filename: 

123 tc.writeFits(filename) 

124 tc2 = lsst.afw.image.TransmissionCurve.readFits(filename) 

125 self.assertTransmissionCurvesEqual(tc, tc2) 

126 

127 def makeAndCheckSpatiallyConstant(self, curve, wavelengths, throughputAtMin, throughputAtMax): 

128 """Construct a constant TransmissionCurve and apply basic tests to it.""" 

129 throughput = curve(wavelengths) 

130 tc = lsst.afw.image.TransmissionCurve.makeSpatiallyConstant(throughput, wavelengths, 

131 throughputAtMin, throughputAtMax) 

132 self.assertEqual(tc.getWavelengthBounds(), (wavelengths[0], wavelengths[-1])) 

133 self.assertEqual(tc.getThroughputAtBounds(), (throughputAtMin, throughputAtMax)) 

134 self.checkEvaluation(tc, wavelengths, throughput) 

135 self.checkEvaluation(tc, np.array([2*wavelengths[0] - wavelengths[1]]), throughputAtMin) 

136 self.checkEvaluation(tc, np.array([2*wavelengths[-1] - wavelengths[-2]]), throughputAtMax) 

137 # Check that we get decent results when interpolating to different wavelengths. 

138 wavelengths1 = np.linspace(wavelengths[0] - 10, wavelengths[-1] + 10, 200) 

139 self.checkEvaluation(tc, wavelengths1, curve(wavelengths1), rtol=1E-2, atol=1E-2) 

140 # Test that multiplication with identity is a no-op 

141 tc2 = tc * lsst.afw.image.TransmissionCurve.makeIdentity() 

142 self.assertTransmissionCurvesEqual(tc, tc2) 

143 return tc 

144 

145 def checkSpatiallyConstantEvenSpacing(self, throughputAtMin, throughputAtMax): 

146 """Test that we can construct and use a spatially-constant 

147 TransmissionCurve initialized with an evenly-spaced wavelength 

148 array. 

149 """ 

150 throughputAtMin = self.randIfNone(throughputAtMin) 

151 throughputAtMax = self.randIfNone(throughputAtMax) 

152 wavelengths = np.linspace(self.minWavelength, self.maxWavelength, 100) 

153 curve = makeTestCurve(self.random, self.minWavelength, self.maxWavelength, 

154 throughputAtMin, throughputAtMax) 

155 tc = self.makeAndCheckSpatiallyConstant(curve, wavelengths, throughputAtMin, throughputAtMax) 

156 self.checkPersistence(tc) 

157 

158 def testSpatiallyConstantEvenSpacing(self): 

159 """Invoke all SpatiallyConstantEvenSpacing tests. 

160 

161 Should be updated to use subTest when Python < 3.4 support is ended. 

162 """ 

163 self.checkSpatiallyConstantEvenSpacing(0.0, 0.0) 

164 self.checkSpatiallyConstantEvenSpacing(0.0, None) 

165 self.checkSpatiallyConstantEvenSpacing(None, 0.0) 

166 self.checkSpatiallyConstantEvenSpacing(None, None) 

167 

168 def checkSpatiallyConstantUnevenSpacing(self, throughputAtMin, throughputAtMax): 

169 """Test that we can construct and use a spatially-constant 

170 TransmissionCurve initialized with an unevenly-spaced wavelength 

171 array. 

172 """ 

173 throughputAtMin = self.randIfNone(throughputAtMin) 

174 throughputAtMax = self.randIfNone(throughputAtMax) 

175 wavelengths = self.minWavelength + (self.maxWavelength - self.minWavelength)*self.random.rand(100) 

176 wavelengths.sort() 

177 curve = makeTestCurve(self.random, self.minWavelength, self.maxWavelength, 

178 throughputAtMin, throughputAtMax) 

179 tc = self.makeAndCheckSpatiallyConstant(curve, wavelengths, throughputAtMin, throughputAtMax) 

180 self.checkPersistence(tc) 

181 

182 def testSpatiallyConstantUnevenSpacing(self): 

183 """Invoke all SpatiallyConstantUnevenSpacing tests. 

184 

185 Should be updated to use subTest when Python < 3.4 support is ended. 

186 """ 

187 self.checkSpatiallyConstantUnevenSpacing(0.0, 0.0) 

188 self.checkSpatiallyConstantUnevenSpacing(0.0, None) 

189 self.checkSpatiallyConstantUnevenSpacing(None, 0.0) 

190 self.checkSpatiallyConstantUnevenSpacing(None, None) 

191 

192 def checkProduct(self, throughputAtMin1, throughputAtMax1, throughputAtMin2, throughputAtMax2): 

193 """Test the product of two spatially-constant TransmissionCurves.""" 

194 throughputAtMin1 = self.randIfNone(throughputAtMin1) 

195 throughputAtMax1 = self.randIfNone(throughputAtMax1) 

196 throughputAtMin2 = self.randIfNone(throughputAtMin2) 

197 throughputAtMax2 = self.randIfNone(throughputAtMax2) 

198 wl1a = 5100 + self.random.rand() 

199 wl1b = 5500 + self.random.rand() 

200 wl2a = 5200 + self.random.rand() 

201 wl2b = 5600 + self.random.rand() 

202 wl1 = np.linspace(wl1a, wl1b, 100) 

203 wl2 = np.linspace(wl2a, wl2b, 100) 

204 curve1 = makeTestCurve(self.random, wl1a, wl1b, throughputAtMin1, throughputAtMax1) 

205 curve2 = makeTestCurve(self.random, wl2a, wl2b, throughputAtMin2, throughputAtMax2) 

206 op1 = self.makeAndCheckSpatiallyConstant(curve1, wl1, throughputAtMin1, throughputAtMax1) 

207 op2 = self.makeAndCheckSpatiallyConstant(curve2, wl2, throughputAtMin2, throughputAtMax2) 

208 product = op1 * op2 

209 

210 lowest = np.linspace(wl1a - 10, wl1a - 1, 10) 

211 self.checkEvaluation(product, lowest, throughputAtMin1*throughputAtMin2) 

212 

213 lower = np.linspace(wl1a + 1, wl2a - 1, 10) 

214 if throughputAtMin2 == 0.0: 

215 self.checkEvaluation(product, lower, 0.0) 

216 else: 

217 for point in self.points: 

218 self.assertFloatsEqual( 

219 op1.sampleAt(point, lower)*throughputAtMin2, 

220 product.sampleAt(point, lower) 

221 ) 

222 

223 inner = np.linspace(wl2a, wl1b, 10) 

224 for point in self.points: 

225 self.assertFloatsAlmostEqual( 

226 op1.sampleAt(point, inner)*op2.sampleAt(point, inner), 

227 product.sampleAt(point, inner) 

228 ) 

229 

230 upper = np.linspace(wl1b + 1, wl2b - 1, 10) 

231 if throughputAtMax1 == 0.0: 

232 self.checkEvaluation(product, upper, 0.0) 

233 else: 

234 for point in self.points: 

235 self.assertFloatsEqual( 

236 op2.sampleAt(point, upper)*throughputAtMax1, 

237 product.sampleAt(point, upper) 

238 ) 

239 

240 uppermost = np.linspace(wl2b + 1, wl2b + 10, 10) 

241 self.checkEvaluation(product, uppermost, throughputAtMax1*throughputAtMax2) 

242 

243 self.checkPersistence(product) 

244 

245 def testProduct(self): 

246 """Invoke all checkProduct tests. 

247 

248 Should be updated to use subTest when Python < 3.4 support is ended. 

249 """ 

250 self.checkProduct(0.0, 0.0, 0.0, 0.0) 

251 self.checkProduct(0.0, 0.0, 0.0, None) 

252 self.checkProduct(0.0, 0.0, None, 0.0) 

253 self.checkProduct(0.0, 0.0, None, None) 

254 self.checkProduct(0.0, None, 0.0, 0.0) 

255 self.checkProduct(0.0, None, 0.0, None) 

256 self.checkProduct(0.0, None, None, 0.0) 

257 self.checkProduct(0.0, None, None, None) 

258 self.checkProduct(None, 0.0, 0.0, 0.0) 

259 self.checkProduct(None, 0.0, 0.0, None) 

260 self.checkProduct(None, 0.0, None, 0.0) 

261 self.checkProduct(None, 0.0, None, None) 

262 self.checkProduct(None, None, 0.0, 0.0) 

263 self.checkProduct(None, None, 0.0, None) 

264 self.checkProduct(None, None, None, 0.0) 

265 self.checkProduct(None, None, None, None) 

266 

267 def makeRadial(self): 

268 """Construct a random radial TransmissionCurve and return it with 

269 the wavelengths, radii, and 2-d curve used to construct it. 

270 """ 

271 wavelengths = np.linspace(self.minWavelength, self.maxWavelength, 100) 

272 radii = np.linspace(0.0, 1.0, 200) 

273 

274 # This curve will represent the TransmissionCurve at the origin; 

275 # we'll shift it to higher wavelengths and scale it linearly with radius 

276 curve = makeTestCurve(self.random, wavelengths[0], wavelengths[90]) 

277 delta = (wavelengths[1] - wavelengths[0])/(radii[1] - radii[0]) 

278 

279 def curve2d(lam, r): 

280 return curve(lam + delta*r)*(1.0+r) 

281 

282 throughput = np.zeros(wavelengths.shape + radii.shape, dtype=float) 

283 for i, radius in enumerate(radii): 

284 throughput[:, i] = curve2d(wavelengths, radius) 

285 

286 tc = lsst.afw.image.TransmissionCurve.makeRadial(throughput, wavelengths, radii) 

287 

288 return tc, wavelengths, radii, curve2d 

289 

290 def testRadial(self): 

291 """Test the functionality of radial TransmissionCurves.""" 

292 tc, wavelengths, radii, curve2d = self.makeRadial() 

293 

294 # Test at exactly the radii and wavelengths we initialized with. 

295 for n, radius in enumerate(radii): 

296 rot = lsst.geom.LinearTransform.makeRotation( 

297 2.0*np.pi*self.random.rand()*lsst.geom.radians 

298 ) 

299 p0 = lsst.geom.Point2D(0.0, radius) 

300 p1 = rot(p0) 

301 self.assertFloatsAlmostEqual( 

302 curve2d(wavelengths, radius), 

303 tc.sampleAt(p0, wavelengths), 

304 rtol=1E-13 

305 ) 

306 self.assertFloatsAlmostEqual( 

307 curve2d(wavelengths, radius), 

308 tc.sampleAt(p1, wavelengths), 

309 rtol=1E-13 

310 ) 

311 

312 # Test at some other random points in radius and wavelength. 

313 wl2 = np.linspace(self.minWavelength, self.maxWavelength, 151) 

314 for point in self.points: 

315 radius = (point.getX()**2 + point.getY()**2)**0.5 

316 self.assertFloatsAlmostEqual( 

317 curve2d(wl2, radius), 

318 tc.sampleAt(point, wl2), 

319 rtol=1E-2, atol=1E-2 

320 ) 

321 

322 # Test persistence for radial TransmissionCurves 

323 self.checkPersistence(tc) 

324 

325 def testTransform(self): 

326 """Test that we can transform a spatially-varying TransmissionCurve.""" 

327 tc, wavelengths, radii, curve2d = self.makeRadial() 

328 

329 # If we transform by a pure rotation, what we get back should be equivalent. 

330 affine1 = lsst.geom.AffineTransform( 

331 lsst.geom.LinearTransform.makeRotation( 

332 2.0*np.pi*self.random.rand()*lsst.geom.radians 

333 ) 

334 ) 

335 transform1 = lsst.afw.geom.makeTransform(affine1) 

336 wl2 = np.linspace(self.minWavelength, self.maxWavelength, 151) 

337 tc1 = tc.transformedBy(transform1) 

338 self.assertTransmissionCurvesEqual(tc, tc1, rtol=1E-13) 

339 

340 # Test transforming by a random affine transform. 

341 affine2 = lsst.geom.AffineTransform( 

342 lsst.geom.LinearTransform.makeScaling(1.0 + self.random.rand()), 

343 lsst.geom.Extent2D(self.random.randn(), self.random.randn()) 

344 ) 

345 transform2 = lsst.afw.geom.makeTransform(affine2) 

346 tc2 = tc.transformedBy(transform2) 

347 for point in self.points: 

348 self.assertFloatsAlmostEqual( 

349 tc.sampleAt(point, wl2), 

350 tc2.sampleAt(affine2(point), wl2), 

351 rtol=1E-13 

352 ) 

353 

354 # Test further transforming the rotated transmission curve 

355 tc3 = tc1.transformedBy(transform2) 

356 self.assertTransmissionCurvesEqual(tc2, tc3) 

357 

358 # Test persistence for transformed TransmissionCurves 

359 self.checkPersistence(tc3) 

360 

361 def testExposure(self): 

362 """Test that we can attach a TransmissionCurve to an Exposure and round-trip it through I/O.""" 

363 wavelengths = np.linspace(6200, 6400, 100) 

364 curve = makeTestCurve(self.random, 6200, 6400, 0.0, 0.0) 

365 tc1 = self.makeAndCheckSpatiallyConstant(curve, wavelengths, 0.0, 0.0) 

366 exposure1 = lsst.afw.image.ExposureF(4, 5) 

367 exposure1.getInfo().setTransmissionCurve(tc1) 

368 self.assertTrue(exposure1.getInfo().hasTransmissionCurve()) 

369 with lsst.utils.tests.getTempFilePath(".fits") as filename: 

370 exposure1.writeFits(filename) 

371 exposure2 = lsst.afw.image.ExposureF(filename) 

372 self.assertTrue(exposure2.getInfo().hasTransmissionCurve()) 

373 tc2 = exposure2.getInfo().getTransmissionCurve() 

374 self.assertTransmissionCurvesEqual(tc1, tc2) 

375 

376 def testExposureRecord(self): 

377 """Test that we can attach a TransmissionCurve to an ExposureRecord and round-trip it through I/O.""" 

378 wavelengths = np.linspace(6200, 6400, 100) 

379 curve = makeTestCurve(self.random, 6200, 6400, 0.0, 0.0) 

380 tc1 = self.makeAndCheckSpatiallyConstant(curve, wavelengths, 0.0, 0.0) 

381 cat1 = lsst.afw.table.ExposureCatalog(lsst.afw.table.ExposureTable.makeMinimalSchema()) 

382 cat1.addNew().setTransmissionCurve(tc1) 

383 self.assertTrue(cat1[0].getTransmissionCurve() is not None) 

384 with lsst.utils.tests.getTempFilePath(".fits") as filename: 

385 cat1.writeFits(filename) 

386 cat2 = lsst.afw.table.ExposureCatalog.readFits(filename) 

387 self.assertTrue(cat2[0].getTransmissionCurve() is not None) 

388 tc2 = cat2[0].getTransmissionCurve() 

389 self.assertTransmissionCurvesEqual(tc1, tc2) 

390 

391 

392class MemoryTester(lsst.utils.tests.MemoryTestCase): 

393 pass 

394 

395 

396def setup_module(module): 

397 lsst.utils.tests.init() 

398 

399 

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

401 lsst.utils.tests.init() 

402 unittest.main()