Coverage for tests / test_chebyshevBoundedField.py: 12%

314 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-25 01:20 -0700

1# This file is part of afw. 

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 

22""" 

23Tests for math.ChebyshevBoundedField 

24 

25Run with: 

26 python test_chebyshevBoundedField.py 

27or 

28 pytest test_chebyshevBoundedField.py 

29""" 

30 

31import os 

32import unittest 

33 

34import numpy as np 

35 

36import lsst.utils.tests 

37import lsst.pex.exceptions 

38import lsst.geom 

39import lsst.afw.image 

40import lsst.afw.math 

41import lsst.afw.geom 

42 

43testPath = os.path.abspath(os.path.dirname(__file__)) 

44 

45CHEBYSHEV_T = [ 

46 lambda x: x**0, 

47 lambda x: x, 

48 lambda x: 2*x**2 - 1, 

49 lambda x: (4*x**2 - 3)*x, 

50 lambda x: (8*x**2 - 8)*x**2 + 1, 

51 lambda x: ((16*x**2 - 20)*x**2 + 5)*x, 

52] 

53 

54 

55def multiply(image, field): 

56 """Return the product of image and field() at each point in image. 

57 """ 

58 box = image.getBBox() 

59 outImage = lsst.afw.image.ImageF(box) 

60 for i in range(box.getMinX(), box.getMaxX() + 1): 

61 for j in range(box.getMinY(), box.getMaxY() + 1): 

62 outImage[i, j] = image[i, j]*field.evaluate(i, j) 

63 return outImage 

64 

65 

66def divide(image, field): 

67 """Return the quotient of image and field() at each point in image. 

68 """ 

69 box = image.getBBox() 

70 outImage = lsst.afw.image.ImageF(box) 

71 for i in range(box.getMinX(), box.getMaxX() + 1): 

72 for j in range(box.getMinY(), box.getMaxY() + 1): 

73 outImage[i, j] = image[i, j]/field.evaluate(i, j) 

74 return outImage 

75 

76 

77class ChebyshevBoundedFieldTestCase(lsst.utils.tests.TestCase): 

78 

79 def setUp(self): 

80 self.longMessage = True 

81 np.random.seed(5) 

82 self.bbox = lsst.geom.Box2I( 

83 lsst.geom.Point2I(-5, -5), lsst.geom.Point2I(5, 5)) 

84 self.x1d = np.linspace(self.bbox.getBeginX(), self.bbox.getEndX()) 

85 self.y1d = np.linspace(self.bbox.getBeginY(), self.bbox.getEndY()) 

86 self.x2d, self.y2d = np.meshgrid(self.x1d, self.y1d) 

87 self.xFlat = np.ravel(self.x2d) 

88 self.yFlat = np.ravel(self.y2d) 

89 self.cases = [] 

90 for orderX in range(0, 5): 

91 for orderY in range(0, 5): 

92 indexX, indexY = np.meshgrid(np.arange(orderX + 1, dtype=int), 

93 np.arange(orderY + 1, dtype=int)) 

94 for triangular in (True, False): 

95 ctrl = lsst.afw.math.ChebyshevBoundedFieldControl() 

96 ctrl.orderX = orderX 

97 ctrl.orderY = orderY 

98 ctrl.triangular = triangular 

99 coefficients = np.random.randn(orderY + 1, orderX + 1) 

100 if triangular: 

101 coefficients[indexX + indexY > max(orderX, orderY)] = 0.0 

102 self.cases.append((ctrl, coefficients)) 

103 

104 array = np.arange(self.bbox.getArea(), dtype=np.float32).reshape(self.bbox.getDimensions()) 

105 self.image = lsst.afw.image.ImageF(array) 

106 self.fields = [lsst.afw.math.ChebyshevBoundedField(self.bbox, coeffs) for _, coeffs in self.cases] 

107 self.product = lsst.afw.math.ProductBoundedField(self.fields) 

108 

109 def tearDown(self): 

110 del self.bbox 

111 

112 def testFillImageInterpolation(self): 

113 ctrl, coefficients = self.cases[-2] 

114 bbox = lsst.geom.Box2I(lsst.geom.Point2I(10, 15), 

115 lsst.geom.Extent2I(360, 350)) 

116 field = lsst.afw.math.ChebyshevBoundedField(bbox, coefficients) 

117 image1 = lsst.afw.image.ImageF(bbox) 

118 image2 = lsst.afw.image.ImageF(bbox) 

119 image3 = lsst.afw.image.ImageF(bbox) 

120 image4 = lsst.afw.image.ImageF(bbox) 

121 field.fillImage(image1) 

122 field.fillImage(image2, xStep=3) 

123 field.fillImage(image3, yStep=4) 

124 field.fillImage(image4, xStep=3, yStep=4) 

125 self.assertFloatsAlmostEqual(image1.array, image2.array, rtol=1E-2, atol=1E-2) 

126 self.assertFloatsAlmostEqual(image1.array, image3.array, rtol=1.5E-2, atol=1.5E-2) 

127 self.assertFloatsAlmostEqual(image1.array, image4.array, rtol=2E-2, atol=2E-2) 

128 

129 def testEvaluate(self): 

130 """Test the single-point evaluate method against explicitly-defined 1-d Chebyshevs 

131 (at the top of this file). 

132 """ 

133 factor = 12.345 

134 boxD = lsst.geom.Box2D(self.bbox) 

135 # sx, sy: transform from self.bbox range to [-1, -1] 

136 sx = 2.0/boxD.getWidth() 

137 sy = 2.0/boxD.getHeight() 

138 nPoints = 50 

139 for ctrl, coefficients in self.cases: 

140 field = lsst.afw.math.ChebyshevBoundedField( 

141 self.bbox, coefficients) 

142 x = np.random.rand(nPoints)*boxD.getWidth() + boxD.getMinX() 

143 y = np.random.rand(nPoints)*boxD.getHeight() + boxD.getMinY() 

144 z1 = field.evaluate(x, y) 

145 tx = np.array([CHEBYSHEV_T[i](sx*x) 

146 for i in range(coefficients.shape[1])]) 

147 ty = np.array([CHEBYSHEV_T[i](sy*y) 

148 for i in range(coefficients.shape[0])]) 

149 self.assertEqual(tx.shape, (coefficients.shape[1], x.size)) 

150 self.assertEqual(ty.shape, (coefficients.shape[0], y.size)) 

151 z2 = np.array([np.dot(ty[:, i], np.dot(coefficients, tx[:, i])) 

152 for i in range(nPoints)]) 

153 self.assertFloatsAlmostEqual(z1, z2, rtol=1E-12) 

154 

155 scaled = field*factor 

156 self.assertFloatsAlmostEqual(scaled.evaluate(x, y), 

157 factor*z2, 

158 rtol=factor*1E-13) 

159 self.assertFloatsEqual( 

160 scaled.getCoefficients(), factor*field.getCoefficients()) 

161 

162 def testProductEvaluate(self): 

163 """Test that ProductBoundedField.evaluate is equivalent to multiplying 

164 its nested BoundedFields. 

165 """ 

166 zFlat1 = self.product.evaluate(self.xFlat, self.yFlat) 

167 zFlat2 = np.array([self.product.evaluate(x, y) for x, y in zip(self.xFlat, self.yFlat)]) 

168 self.assertFloatsAlmostEqual(zFlat1, zFlat2) 

169 zFlat3 = np.ones(zFlat1.shape, dtype=float) 

170 for field in self.fields: 

171 zFlat3 *= field.evaluate(self.xFlat, self.yFlat) 

172 self.assertFloatsAlmostEqual(zFlat1, zFlat3) 

173 

174 def testMultiplyImage(self): 

175 """Test multiplying an image in place. 

176 """ 

177 _, coefficients = self.cases[-2] 

178 field = lsst.afw.math.ChebyshevBoundedField(self.image.getBBox(), coefficients) 

179 # multiplyImage() is in-place, so we have to make the expected result first. 

180 expect = multiply(self.image, field) 

181 field.multiplyImage(self.image) 

182 self.assertImagesAlmostEqual(self.image, expect) 

183 

184 def testMultiplyMaskedImage(self): 

185 """Test multiplying a masked image in place. 

186 """ 

187 _, coefficients = self.cases[-2] 

188 field = lsst.afw.math.ChebyshevBoundedField(self.image.getBBox(), coefficients) 

189 # multiplyImage() is in-place, so we have to make the expected result first. 

190 ones = lsst.afw.image.ImageF(self.image.getBBox()) 

191 ones.array[:, :] = 1.0 

192 expect_masked_image = lsst.afw.image.MaskedImageF( 

193 multiply(self.image, field), 

194 None, 

195 multiply(multiply(ones, field), field) 

196 ) 

197 masked_image = lsst.afw.image.MaskedImageF(self.image) 

198 masked_image.variance.array[:, :] = 1.0 

199 field.multiplyImage(masked_image) 

200 self.assertImagesAlmostEqual(masked_image.image, expect_masked_image.image) 

201 self.assertMaskedImagesAlmostEqual(masked_image, expect_masked_image) 

202 

203 def testDivideImage(self): 

204 """Test dividing an image in place. 

205 """ 

206 _, coefficients = self.cases[-2] 

207 field = lsst.afw.math.ChebyshevBoundedField(self.image.getBBox(), coefficients) 

208 # divideImage() is in-place, so we have to make the expected result first. 

209 expect = divide(self.image, field) 

210 field.divideImage(self.image) 

211 self.assertImagesAlmostEqual(self.image, expect) 

212 

213 def testDivideMaskedImage(self): 

214 """Test dividing a masked image in place. 

215 """ 

216 _, coefficients = self.cases[-2] 

217 field = lsst.afw.math.ChebyshevBoundedField(self.image.getBBox(), coefficients) 

218 # divideImage() is in-place, so we have to make the expected result first. 

219 ones = lsst.afw.image.ImageF(self.image.getBBox()) 

220 ones.array[:, :] = 1.0 

221 expect_masked_image = lsst.afw.image.MaskedImageF( 

222 divide(self.image, field), 

223 None, 

224 divide(divide(ones, field), field) 

225 ) 

226 masked_image = lsst.afw.image.MaskedImageF(self.image) 

227 masked_image.variance.array[:, :] = 1.0 

228 field.divideImage(masked_image) 

229 self.assertImagesAlmostEqual(masked_image.image, expect_masked_image.image) 

230 self.assertMaskedImagesAlmostEqual(masked_image, expect_masked_image) 

231 

232 def testMultiplyImageRaisesUnequalBBox(self): 

233 """Multiplying an image with a different bbox should raise. 

234 """ 

235 _, coefficients = self.cases[-2] 

236 field = lsst.afw.math.ChebyshevBoundedField(self.image.getBBox(), coefficients) 

237 subBox = lsst.geom.Box2I(lsst.geom.Point2I(0, 3), lsst.geom.Point2I(3, 4)) 

238 subImage = self.image.subset(subBox) 

239 with self.assertRaises(RuntimeError): 

240 field.multiplyImage(subImage) 

241 

242 def testMultiplyImageOverlapSubImage(self): 

243 """Multiplying a subimage with overlapOnly=true should only modify 

244 the subimage, when a subimage is passed in. 

245 """ 

246 _, coefficients = self.cases[-2] 

247 field = lsst.afw.math.ChebyshevBoundedField(self.image.getBBox(), coefficients) 

248 subBox = lsst.geom.Box2I(lsst.geom.Point2I(0, 3), lsst.geom.Point2I(3, 4)) 

249 subImage = self.image.subset(subBox) 

250 expect = self.image.clone() 

251 expect[subBox] = multiply(subImage, field) 

252 field.multiplyImage(subImage, overlapOnly=True) 

253 self.assertImagesAlmostEqual(self.image, expect) 

254 

255 def testMultiplyImageOverlapSmallerBoundedField(self): 

256 """Multiplying a subimage with overlapOnly=true should only modify 

257 the subimage if the boundedField bbox is smaller than the image. 

258 

259 This is checking for a bug where the bounded field was writing outside 

260 the overlap bbox. 

261 """ 

262 _, coefficients = self.cases[-2] 

263 subBox = lsst.geom.Box2I(lsst.geom.Point2I(0, 3), lsst.geom.Point2I(3, 4)) 

264 # The BF is only defined on the subBox, not the whole image bbox. 

265 field = lsst.afw.math.ChebyshevBoundedField(subBox, coefficients) 

266 subImage = self.image.subset(subBox) 

267 expect = self.image.clone() 

268 expect[subBox] = multiply(subImage, field) 

269 field.multiplyImage(self.image, overlapOnly=True) 

270 self.assertImagesAlmostEqual(self.image, expect) 

271 

272 def _testIntegrateBox(self, bbox, coeffs, expect): 

273 field = lsst.afw.math.ChebyshevBoundedField(bbox, coeffs) 

274 self.assertFloatsAlmostEqual(field.integrate(), expect, rtol=1E-14) 

275 

276 def testIntegrateTrivialBox(self): 

277 """Test integrating over a "trivial" [-1,1] box. 

278 

279 NOTE: a "trivial" BBox can't be constructed exactly, given that Box2I 

280 is inclusive, but the [0,1] box has the same area (because it is 

281 actually (-0.5, 1.5) when converted to a Box2D), and the translation 

282 doesn't affect the integral. 

283 """ 

284 bbox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0), 

285 lsst.geom.Point2I(1, 1)) 

286 

287 # 0th order polynomial 

288 coeffs = np.array([[5.0]]) 

289 self._testIntegrateBox(bbox, coeffs, 4.0*coeffs[0, 0]) 

290 

291 # 1st order polynomial: odd orders drop out of integral 

292 coeffs = np.array([[5.0, 2.0], [3.0, 4.0]]) 

293 self._testIntegrateBox(bbox, coeffs, 4.0*coeffs[0, 0]) 

294 

295 # 2nd order polynomial in x, 0th in y 

296 coeffs = np.array([[5.0, 0.0, 7.0]]) 

297 self._testIntegrateBox( 

298 bbox, coeffs, 4.0*coeffs[0, 0] - (4.0/3.0)*coeffs[0, 2]) 

299 

300 # 2nd order polynomial in y, 0th in x 

301 coeffs = np.zeros((3, 3)) 

302 coeffs[0, 0] = 5.0 

303 coeffs[2, 0] = 7.0 

304 self._testIntegrateBox( 

305 bbox, coeffs, 4.0*coeffs[0, 0] - (4.0/3.0)*coeffs[2, 0]) 

306 

307 # 2nd order polynomial in x and y, no cross-term 

308 coeffs = np.zeros((3, 3)) 

309 coeffs[0, 0] = 5.0 

310 coeffs[1, 0] = 7.0 

311 coeffs[0, 2] = 3.0 

312 self._testIntegrateBox(bbox, coeffs, 

313 4.0*coeffs[0, 0] - (4.0/3.0)*coeffs[2, 0] - (4.0/3.0)*coeffs[0, 2]) 

314 

315 def testIntegrateBox(self): 

316 r"""Test integrating over an "interesting" box. 

317 

318 The values of these integrals were checked in Mathematica. The code 

319 block below can be pasted into Mathematica to re-do those calculations. 

320 

321 :: 

322 

323 f[x_, y_, n_, m_] := \!\( 

324 \*UnderoverscriptBox[\(\[Sum]\), \(i = 0\), \(n\)]\( 

325 \*UnderoverscriptBox[\(\[Sum]\), \(j = 0\), \(m\)] 

326 \*SubscriptBox[\(a\), \(i, j\)]*ChebyshevT[i, x]*ChebyshevT[j, y]\)\) 

327 integrate2dBox[n_, m_, x0_, x1_, y0_, y1_] := \!\( 

328 \*SubsuperscriptBox[\(\[Integral]\), \(y0\), \(y1\)]\( 

329 \*SubsuperscriptBox[\(\[Integral]\), \(x0\), \(x1\)]f[ 

330 \*FractionBox[\(2 x - x0 - x1\), \(x1 - x0\)], 

331 \*FractionBox[\(2 y - y0 - y1\), \(y1 - y0\)], n, 

332 m] \[DifferentialD]x \[DifferentialD]y\)\) 

333 integrate2dBox[0, 0, -2.5, 5.5, -3.5, 7.5] 

334 integrate2dBox[1, 0, -2.5, 5.5, -3.5, 7.5] 

335 integrate2dBox[0, 1, -2.5, 5.5, -3.5, 7.5] 

336 integrate2dBox[1, 1, -2.5, 5.5, -3.5, 7.5] 

337 integrate2dBox[1, 2, -2.5, 5.5, -3.5, 7.5] 

338 integrate2dBox[2, 2, -2.5, 5.5, -3.5, 7.5] 

339 """ 

340 bbox = lsst.geom.Box2I( 

341 lsst.geom.Point2I(-2, -3), lsst.geom.Point2I(5, 7)) 

342 

343 # 0th order polynomial 

344 coeffs = np.array([[5.0]]) 

345 self._testIntegrateBox(bbox, coeffs, 88.0*coeffs[0, 0]) 

346 

347 # 1st order polynomial: odd orders drop out of integral 

348 coeffs = np.array([[5.0, 2.0], [3.0, 4.0]]) 

349 self._testIntegrateBox(bbox, coeffs, 88.0*coeffs[0, 0]) 

350 

351 # 2nd order polynomial in x, 0th in y 

352 coeffs = np.array([[5.0, 0.0, 7.0]]) 

353 self._testIntegrateBox( 

354 bbox, coeffs, 88.0*coeffs[0, 0] - (88.0/3.0)*coeffs[0, 2]) 

355 

356 # 2nd order polynomial in y, 0th in x 

357 coeffs = np.zeros((3, 3)) 

358 coeffs[0, 0] = 5.0 

359 coeffs[2, 0] = 7.0 

360 self._testIntegrateBox( 

361 bbox, coeffs, 88.0*coeffs[0, 0] - (88.0/3.0)*coeffs[2, 0]) 

362 

363 # 2nd order polynomial in x,y 

364 coeffs = np.zeros((3, 3)) 

365 coeffs[2, 2] = 11.0 

366 self._testIntegrateBox(bbox, coeffs, (88.0/9.0)*coeffs[2, 2]) 

367 

368 def testMean(self): 

369 """The mean of the nth 1d Chebyshev (a_n*T_n(x)) on [-1,1] is 

370 0 for odd n 

371 a_n / (1-n^2) for even n 

372 

373 Similarly, the mean of the (n,m)th 2d Chebyshev is the appropriate 

374 product of the above. 

375 """ 

376 bbox = lsst.geom.Box2I( 

377 lsst.geom.Point2I(-2, -3), lsst.geom.Point2I(5, 7)) 

378 

379 coeffs = np.array([[5.0]]) 

380 field = lsst.afw.math.ChebyshevBoundedField(bbox, coeffs) 

381 self.assertEqual(field.mean(), coeffs[0, 0]) 

382 

383 coeffs = np.array([[5.0, 0.0, 3.0]]) 

384 field = lsst.afw.math.ChebyshevBoundedField(bbox, coeffs) 

385 self.assertEqual(field.mean(), coeffs[0, 0] - coeffs[0, 2]/3.0) 

386 

387 # 2nd order polynomial in x,y 

388 coeffs = np.zeros((3, 3)) 

389 coeffs[0, 0] = 7.0 

390 coeffs[1, 0] = 31.0 

391 coeffs[0, 2] = 13.0 

392 coeffs[2, 2] = 11.0 

393 field = lsst.afw.math.ChebyshevBoundedField(bbox, coeffs) 

394 self.assertFloatsAlmostEqual( 

395 field.mean(), coeffs[0, 0] - coeffs[0, 2]/3.0 + coeffs[2, 2]/9.0) 

396 

397 def testImageFit(self): 

398 """Test that we can fit an image produced by a ChebyshevBoundedField and 

399 get the same coefficients back. 

400 """ 

401 for ctrl, coefficients in self.cases: 

402 inField = lsst.afw.math.ChebyshevBoundedField( 

403 self.bbox, coefficients) 

404 for Image in (lsst.afw.image.ImageF, lsst.afw.image.ImageD): 

405 image = Image(self.bbox) 

406 inField.fillImage(image) 

407 outField = lsst.afw.math.ChebyshevBoundedField.fit(image, ctrl) 

408 self.assertFloatsAlmostEqual( 

409 outField.getCoefficients(), coefficients, rtol=1E-6, atol=1E-7) 

410 

411 def testArrayFit(self): 

412 """Test that we can fit 1-d arrays produced by a ChebyshevBoundedField and 

413 get the same coefficients back. 

414 """ 

415 for ctrl, coefficients in self.cases: 

416 inField = lsst.afw.math.ChebyshevBoundedField( 

417 self.bbox, coefficients) 

418 for Image in (lsst.afw.image.ImageF, lsst.afw.image.ImageD): 

419 array = inField.evaluate(self.xFlat, self.yFlat) 

420 outField1 = lsst.afw.math.ChebyshevBoundedField.fit(self.bbox, self.xFlat, self.yFlat, 

421 array, ctrl) 

422 self.assertFloatsAlmostEqual( 

423 outField1.getCoefficients(), coefficients, rtol=1E-6, atol=1E-7) 

424 weights = (1.0 + np.random.randn(array.size)**2) 

425 # Should get same results with different weights, since we still have no noise 

426 # and a model that can exactly reproduce the data. 

427 outField2 = lsst.afw.math.ChebyshevBoundedField.fit(self.bbox, self.xFlat, self.yFlat, 

428 array, weights, ctrl) 

429 self.assertFloatsAlmostEqual( 

430 outField2.getCoefficients(), coefficients, rtol=1E-7, atol=1E-7) 

431 # If we make a matrix for the same points and use it to fit, 

432 # we get coefficients in unspecified order, but there are 

433 # (up to round-off error) the same as the nonzero entries of 

434 # the 2-d coefficients arrays. 

435 matrix = lsst.afw.math.ChebyshevBoundedField.makeFitMatrix(self.bbox, self.xFlat, self.yFlat, 

436 ctrl) 

437 coefficientsPacked, _, _, _ = np.linalg.lstsq(matrix, array) 

438 coefficientsPacked.sort() 

439 coefficientsFlat = coefficients.flatten() 

440 coefficientsFlat = coefficientsFlat[np.abs(coefficientsFlat) > 1E-7] 

441 coefficientsFlat.sort() 

442 self.assertFloatsAlmostEqual(coefficientsFlat, coefficientsPacked, rtol=1E-6, atol=1E-7) 

443 

444 def testApproximate(self): 

445 """Test the approximate instantiation with the example of 

446 fitting a PixelAreaBoundedField to reasonable precision. 

447 """ 

448 

449 # This HSC-R band wcs was chosen arbitrarily from the edge of 

450 # field-of-view (ccd 4) for the w_2019_38 processing of RC2 as it 

451 # represents the typical use case of the approxBoundedField method. 

452 skyWcs = lsst.afw.geom.SkyWcs.readFits(os.path.join(testPath, 

453 "data/jointcal_wcs-0034772-004.fits")) 

454 bbox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0), 

455 lsst.geom.Point2I(2047, 4175)) 

456 

457 pixelAreaField = lsst.afw.math.PixelAreaBoundedField(bbox, skyWcs, 

458 unit=lsst.geom.arcseconds) 

459 approxField = lsst.afw.math.ChebyshevBoundedField.approximate(pixelAreaField) 

460 

461 # Choose random points to test rather than a grid to ensure that 

462 # we are not using the same gridded points as used for the 

463 # approximation. 

464 np.random.seed(seed=1000) 

465 xTest = np.random.uniform(low=0.0, high=bbox.getMaxX(), size=10000) 

466 yTest = np.random.uniform(low=0.0, high=bbox.getMaxY(), size=10000) 

467 

468 # The evaluation of approxField is ~80x faster than the 

469 # evaluation of pixelAreaField. 

470 expect = pixelAreaField.evaluate(xTest, yTest) 

471 result = approxField.evaluate(xTest, yTest) 

472 

473 # The approximation is good to the 1e-7 level (absolute) 

474 self.assertFloatsAlmostEqual(result, expect, atol=1e-7) 

475 # and to the 1e-5 level (relative). This is < 0.01 mmag. 

476 self.assertFloatsAlmostEqual(result, expect, rtol=1e-5) 

477 

478 def testPersistence(self): 

479 """Test that we can round-trip a ChebyshevBoundedField through 

480 persistence. 

481 """ 

482 boxD = lsst.geom.Box2D(self.bbox) 

483 nPoints = 50 

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

485 for ctrl, coefficients in self.cases: 

486 inField = lsst.afw.math.ChebyshevBoundedField( 

487 self.bbox, coefficients) 

488 inField.writeFits(filename) 

489 outField = lsst.afw.math.ChebyshevBoundedField.readFits(filename) 

490 self.assertEqual(inField.getBBox(), outField.getBBox()) 

491 self.assertFloatsAlmostEqual( 

492 inField.getCoefficients(), outField.getCoefficients()) 

493 x = np.random.rand(nPoints)*boxD.getWidth() + boxD.getMinX() 

494 y = np.random.rand(nPoints)*boxD.getHeight() + boxD.getMinY() 

495 z1 = inField.evaluate(x, y) 

496 z2 = inField.evaluate(x, y) 

497 self.assertFloatsAlmostEqual(z1, z2, rtol=1E-13) 

498 

499 # test with an empty bbox 

500 inField = lsst.afw.math.ChebyshevBoundedField(lsst.geom.Box2I(), 

501 np.array([[1.0, 2.0], [3.0, 4.0]])) 

502 inField.writeFits(filename) 

503 outField = lsst.afw.math.ChebyshevBoundedField.readFits(filename) 

504 self.assertEqual(inField.getBBox(), outField.getBBox()) 

505 

506 def testProductPersistence(self): 

507 """Test that we can round-trip a ProductBoundedField through 

508 persistence. 

509 """ 

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

511 self.product.writeFits(filename) 

512 out = lsst.afw.math.ProductBoundedField.readFits(filename) 

513 self.assertEqual(self.product, out) 

514 

515 def testTruncate(self): 

516 """Test that truncate() works as expected. 

517 """ 

518 for ctrl, coefficients in self.cases: 

519 field1 = lsst.afw.math.ChebyshevBoundedField( 

520 self.bbox, coefficients) 

521 field2 = field1.truncate(ctrl) 

522 self.assertFloatsAlmostEqual( 

523 field1.getCoefficients(), field2.getCoefficients()) 

524 self.assertEqual(field1.getBBox(), field2.getBBox()) 

525 config3 = lsst.afw.math.ChebyshevBoundedField.ConfigClass() 

526 config3.readControl(ctrl) 

527 if ctrl.orderX > 0: 

528 config3.orderX -= 1 

529 if ctrl.orderY > 0: 

530 config3.orderY -= 1 

531 field3 = field1.truncate(config3.makeControl()) 

532 for i in range(config3.orderY + 1): 

533 for j in range(config3.orderX + 1): 

534 if config3.triangular and i + j > max(config3.orderX, config3.orderY): 

535 self.assertEqual(field3.getCoefficients()[i, j], 0.0) 

536 else: 

537 self.assertEqual(field3.getCoefficients()[i, j], 

538 field1.getCoefficients()[i, j]) 

539 

540 def testEquality(self): 

541 for ctrl, coefficients in self.cases: 

542 field1 = lsst.afw.math.ChebyshevBoundedField(self.bbox, coefficients) 

543 field2 = lsst.afw.math.ChebyshevBoundedField(self.bbox, coefficients) 

544 self.assertEqual(field1, field2, msg=coefficients) 

545 

546 # same coefficients, instantiated from different arrays 

547 field1 = lsst.afw.math.ChebyshevBoundedField(self.bbox, np.array([[1.0]])) 

548 field2 = lsst.afw.math.ChebyshevBoundedField(self.bbox, np.array([[1.0]])) 

549 self.assertEqual(field1, field2) 

550 field1 = lsst.afw.math.ChebyshevBoundedField(self.bbox, np.array([[1.0, 2.0], [3., 4.]])) 

551 field2 = lsst.afw.math.ChebyshevBoundedField(self.bbox, np.array([[1.0, 2.0], [3., 4.]])) 

552 self.assertEqual(field1, field2) 

553 

554 # different coefficient(s) 

555 field1 = lsst.afw.math.ChebyshevBoundedField(self.bbox, np.array([[1.0]])) 

556 field2 = lsst.afw.math.ChebyshevBoundedField(self.bbox, np.array([[2.0]])) 

557 self.assertNotEqual(field1, field2) 

558 field1 = lsst.afw.math.ChebyshevBoundedField(self.bbox, np.array([[1.0, 0.0]])) 

559 field2 = lsst.afw.math.ChebyshevBoundedField(self.bbox, np.array([[1.0], [0.0]])) 

560 self.assertNotEqual(field1, field2) 

561 

562 # different bbox 

563 bbox1 = lsst.geom.Box2I(lsst.geom.Point2I(-10, -10), lsst.geom.Point2I(5, 5)) 

564 bbox2 = lsst.geom.Box2I(lsst.geom.Point2I(-5, -5), lsst.geom.Point2I(5, 5)) 

565 field1 = lsst.afw.math.ChebyshevBoundedField(bbox1, np.array([[1.0]])) 

566 field2 = lsst.afw.math.ChebyshevBoundedField(bbox2, np.array([[1.0]])) 

567 self.assertNotEqual(field1, field2) 

568 

569 

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

571 pass 

572 

573 

574def setup_module(module): 

575 lsst.utils.tests.init() 

576 

577 

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

579 lsst.utils.tests.init() 

580 unittest.main()