Coverage for tests/test_photoCalib.py: 10%

422 statements  

« prev     ^ index     » next       coverage.py v7.1.0, created at 2023-02-05 17:50 -0800

1# 

2# LSST Data Management System 

3# Copyright 2008-2016 LSST Corporation. 

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 os.path 

24import unittest 

25 

26import numpy as np 

27 

28import astropy.units as u 

29 

30import lsst.utils.tests 

31import lsst.geom 

32import lsst.afw.image 

33import lsst.afw.image.testUtils 

34import lsst.afw.math 

35import lsst.daf.base 

36import lsst.pex.exceptions 

37 

38 

39def computeNanojanskyErr(instFluxErr, instFlux, calibrationErr, calibration, flux): 

40 """Return the error on the flux (nanojansky).""" 

41 return flux*np.hypot(instFluxErr/instFlux, calibrationErr/calibration) 

42 

43 

44def computeMagnitudeErr(instFluxErr, instFlux, calibrationErr, calibration, flux): 

45 """Return the error on the magnitude.""" 

46 err = computeNanojanskyErr(instFluxErr, instFlux, calibrationErr, calibration, flux) 

47 return 2.5/np.log(10) * err / flux 

48 

49 

50def makeCalibratedMaskedImage(image, mask, variance, outImage, calibration, calibrationErr): 

51 """Return a MaskedImage using outImage, mask, and a computed variance image.""" 

52 outErr = computeNanojanskyErr(np.sqrt(variance), 

53 image, 

54 calibrationErr, 

55 calibration, 

56 outImage).astype(np.float32) # variance plane must be 32bit 

57 return lsst.afw.image.makeMaskedImageFromArrays(outImage, 

58 mask, 

59 outErr**2) 

60 

61 

62def makeCalibratedMaskedImageNoCalibrationError(image, mask, variance, outImage): 

63 """Return a MaskedImage using outImage, mask, and a computed variance image. 

64 

65 Ignores the contributions from the uncertainty in the calibration. 

66 """ 

67 outErr = computeNanojanskyErr(np.sqrt(variance), 

68 image, 

69 0, 

70 1, 

71 outImage).astype(np.float32) # variance plane must be 32bit 

72 return lsst.afw.image.makeMaskedImageFromArrays(outImage, 

73 mask, 

74 outErr**2) 

75 

76 

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

78 

79 def setUp(self): 

80 np.random.seed(100) 

81 

82 self.point0 = lsst.geom.Point2D(0, 0) 

83 self.pointXShift = lsst.geom.Point2D(-10, 0) 

84 self.pointYShift = lsst.geom.Point2D(0, -10) 

85 self.bbox = lsst.geom.Box2I(lsst.geom.Point2I(-100, -100), lsst.geom.Point2I(100, 100)) 

86 

87 # calibration and instFlux1 are selected to produce calibrated flux of 1. 

88 self.calibration = 1e-3 

89 self.calibrationErr = 1e-4 

90 self.instFlux1 = 1000. 

91 self.instFluxErr1 = 10. 

92 self.flux1 = 1.0 

93 self.mag1 = (self.flux1*u.nJy).to_value(u.ABmag) 

94 

95 # useful reference points: 575.44 nJy ~= 24.5 mag, 3630.78 * 10^9 nJy ~= 0 mag 

96 self.flux2 = 575.44 

97 self.instFlux2 = self.instFlux1*self.flux2 

98 self.mag2 = (self.flux2*u.nJy).to_value(u.ABmag) 

99 

100 self.schema = lsst.afw.table.SourceTable.makeMinimalSchema() 

101 self.instFluxKeyName = "SomeFlux" 

102 lsst.afw.table.Point2DKey.addFields(self.schema, "centroid", "centroid", "pixels") 

103 self.schema.addField(self.instFluxKeyName+"_instFlux", type="D", units="count", 

104 doc="post-ISR instrumental Flux") 

105 self.schema.addField(self.instFluxKeyName+"_instFluxErr", type="D", units="count", 

106 doc="post-ISR instrumental flux error") 

107 self.schema.addField(self.instFluxKeyName+"_flux", type="D", units="nJy", 

108 doc="calibrated flux") 

109 self.schema.addField(self.instFluxKeyName+"_fluxErr", type="D", units="nJy", 

110 doc="calibrated flux error") 

111 self.schema.addField(self.instFluxKeyName+"_mag", type="D", 

112 doc="calibrated magnitude") 

113 self.schema.addField(self.instFluxKeyName+"_magErr", type="D", 

114 doc="calibrated magnitude error") 

115 self.otherInstFluxKeyName = "OtherFlux" 

116 self.schema.addField(self.otherInstFluxKeyName+"_instFlux", type="D", units="count", 

117 doc="another instrumental Flux") 

118 self.schema.addField(self.otherInstFluxKeyName+"_instFluxErr", type="D", units="count", 

119 doc="another instrumental flux error") 

120 self.noErrInstFluxKeyName = "NoErrFlux" 

121 self.schema.addField(self.noErrInstFluxKeyName+"_instFlux", type="D", units="count", 

122 doc="instrumental Flux with no error") 

123 self.table = lsst.afw.table.SourceTable.make(self.schema) 

124 self.table.defineCentroid('centroid') 

125 self.catalog = lsst.afw.table.SourceCatalog(self.table) 

126 record = self.catalog.addNew() 

127 record.set('id', 1) 

128 record.set('centroid_x', self.point0[0]) 

129 record.set('centroid_y', self.point0[1]) 

130 record.set(self.instFluxKeyName+'_instFlux', self.instFlux1) 

131 record.set(self.instFluxKeyName+'_instFluxErr', self.instFluxErr1) 

132 record.set(self.otherInstFluxKeyName+'_instFlux', self.instFlux1) 

133 record.set(self.otherInstFluxKeyName+'_instFluxErr', self.instFluxErr1) 

134 record.set(self.noErrInstFluxKeyName+'_instFlux', self.instFlux1) 

135 record = self.catalog.addNew() 

136 record.set('id', 2) 

137 record.set('centroid_x', self.pointYShift[0]) 

138 record.set('centroid_y', self.pointYShift[1]) 

139 record.set(self.instFluxKeyName+'_instFlux', self.instFlux2) 

140 record.set(self.instFluxKeyName+'_instFluxErr', self.instFluxErr1) 

141 record.set(self.otherInstFluxKeyName+'_instFlux', self.instFlux2) 

142 record.set(self.otherInstFluxKeyName+'_instFluxErr', self.instFluxErr1) 

143 record.set(self.noErrInstFluxKeyName+'_instFlux', self.instFlux2) 

144 

145 self.constantCalibration = lsst.afw.math.ChebyshevBoundedField(self.bbox, 

146 np.array([[self.calibration]])) 

147 self.linearXCalibration = lsst.afw.math.ChebyshevBoundedField(self.bbox, 

148 np.array([[self.calibration, 

149 self.calibration]])) 

150 

151 def tearDown(self): 

152 del self.schema 

153 del self.table 

154 del self.catalog 

155 

156 def _testPhotoCalibCenter(self, photoCalib, calibrationErr): 

157 """ 

158 Test conversions of instFlux for the mean and (0,0) value of a photoCalib. 

159 Assumes those are the same, e.g. that the non-constant terms are all 

160 odd, and that the mean of the calib is self.calibration. 

161 

162 calibrationErr is provided as an option to allow testing of photoCalibs 

163 that have no error specified, and those that do. 

164 """ 

165 # test that the constructor set the calibrationMean and err correctly 

166 self.assertEqual(self.calibration, photoCalib.getCalibrationMean()) 

167 self.assertEqual(photoCalib.instFluxToMagnitude(photoCalib.getInstFluxAtZeroMagnitude()), 0) 

168 self.assertEqual(calibrationErr, photoCalib.getCalibrationErr()) 

169 

170 # test with a "trivial" flux 

171 self.assertEqual(self.flux1, photoCalib.instFluxToNanojansky(self.instFlux1)) 

172 self.assertEqual(self.mag1, photoCalib.instFluxToMagnitude(self.instFlux1)) 

173 

174 # a less trivial flux 

175 self.assertFloatsAlmostEqual(self.flux2, photoCalib.instFluxToNanojansky(self.instFlux2)) 

176 self.assertFloatsAlmostEqual(self.mag2, photoCalib.instFluxToMagnitude(self.instFlux2)) 

177 # test that (0,0) gives the same result as above 

178 self.assertFloatsAlmostEqual(self.flux2, photoCalib.instFluxToNanojansky(self.instFlux2, self.point0)) 

179 self.assertFloatsAlmostEqual(self.mag2, photoCalib.instFluxToMagnitude(self.instFlux2, self.point0)) 

180 

181 # test that we get a correct nJy err for the base instFlux 

182 errFlux1 = computeNanojanskyErr(self.instFluxErr1, 

183 self.instFlux1, 

184 calibrationErr, 

185 self.calibration, 

186 self.flux1) 

187 result = photoCalib.instFluxToNanojansky(self.instFlux1, self.instFluxErr1) 

188 self.assertEqual(1, result.value) 

189 self.assertFloatsAlmostEqual(errFlux1, result.error) 

190 result = photoCalib.instFluxToNanojansky(self.instFlux1, self.instFluxErr1, self.point0) 

191 self.assertFloatsAlmostEqual(self.flux1, result.value) 

192 self.assertFloatsAlmostEqual(errFlux1, result.error) 

193 

194 # test that we get a correct magnitude err for the base instFlux 

195 errMag1 = computeMagnitudeErr(self.instFluxErr1, 

196 self.instFlux1, 

197 calibrationErr, 

198 self.calibration, 

199 self.flux1) 

200 result = photoCalib.instFluxToMagnitude(self.instFlux1, self.instFluxErr1) 

201 self.assertEqual(self.mag1, result.value) 

202 self.assertFloatsAlmostEqual(errMag1, result.error) 

203 # and the same given an explicit point at the center 

204 result = photoCalib.instFluxToMagnitude(self.instFlux1, self.instFluxErr1, self.point0) 

205 self.assertFloatsAlmostEqual(self.mag1, result.value) 

206 self.assertFloatsAlmostEqual(errMag1, result.error) 

207 

208 # test that we get a correct nJy err for flux2 

209 errFlux2 = computeNanojanskyErr(self.instFluxErr1, 

210 self.instFlux2, 

211 calibrationErr, 

212 self.calibration, 

213 self.flux2) 

214 result = photoCalib.instFluxToNanojansky(self.instFlux2, self.instFluxErr1) 

215 self.assertFloatsAlmostEqual(self.flux2, result.value) 

216 self.assertFloatsAlmostEqual(errFlux2, result.error) 

217 result = photoCalib.instFluxToNanojansky(self.instFlux2, self.instFluxErr1, self.point0) 

218 self.assertFloatsAlmostEqual(self.flux2, result.value) 

219 self.assertFloatsAlmostEqual(errFlux2, result.error) 

220 

221 # test that we get a correct magnitude err for 575 nJy 

222 errMag2 = computeMagnitudeErr(self.instFluxErr1, 

223 self.instFlux2, 

224 calibrationErr, 

225 self.calibration, 

226 self.flux2) 

227 result = photoCalib.instFluxToMagnitude(self.instFlux2, self.instFluxErr1) 

228 self.assertFloatsAlmostEqual(self.mag2, result.value) 

229 self.assertFloatsAlmostEqual(errMag2, result.error) 

230 result = photoCalib.instFluxToMagnitude(self.instFlux2, self.instFluxErr1, self.point0) 

231 self.assertFloatsAlmostEqual(self.mag2, result.value) 

232 self.assertFloatsAlmostEqual(errMag2, result.error) 

233 

234 # test calculations on a single sourceRecord 

235 record = self.catalog[0] 

236 result = photoCalib.instFluxToNanojansky(record, self.instFluxKeyName) 

237 self.assertEqual(self.flux1, result.value) 

238 self.assertFloatsAlmostEqual(errFlux1, result.error) 

239 result = photoCalib.instFluxToMagnitude(record, self.instFluxKeyName) 

240 self.assertEqual(self.mag1, result.value) 

241 self.assertFloatsAlmostEqual(errMag1, result.error) 

242 

243 expectNanojansky = np.array([[self.flux1, errFlux1], [self.flux2, errFlux2]]) 

244 expectMag = np.array([[self.mag1, errMag1], [self.mag2, errMag2]]) 

245 self._testSourceCatalog(photoCalib, self.catalog, expectNanojansky, expectMag) 

246 

247 # test reverse conversion: magnitude to instFlux (no position specified) 

248 self.assertFloatsAlmostEqual(self.instFlux1, photoCalib.magnitudeToInstFlux(self.mag1)) 

249 self.assertFloatsAlmostEqual(self.instFlux2, photoCalib.magnitudeToInstFlux(self.mag2), rtol=1e-15) 

250 

251 # test round-tripping instFlux->magnitude->instFlux (position specified) 

252 mag = photoCalib.instFluxToMagnitude(self.instFlux1, self.pointXShift) 

253 self.assertFloatsAlmostEqual(self.instFlux1, 

254 photoCalib.magnitudeToInstFlux(mag, self.pointXShift), 

255 rtol=1e-15) 

256 mag = photoCalib.instFluxToMagnitude(self.instFlux2, self.pointXShift) 

257 self.assertFloatsAlmostEqual(self.instFlux2, 

258 photoCalib.magnitudeToInstFlux(mag, self.pointXShift), 

259 rtol=1e-15) 

260 

261 # test getLocalCalibration. 

262 meas = photoCalib.instFluxToNanojansky(self.instFlux1, self.instFluxErr1, self.pointXShift) 

263 localCalib = photoCalib.getLocalCalibration(self.pointXShift) 

264 flux = localCalib * self.instFlux1 

265 self.assertAlmostEqual(meas.value, flux) 

266 

267 def _testSourceCatalog(self, photoCalib, catalog, expectNanojansky, expectMag): 

268 """Test instFluxTo*(sourceCatalog, ...), and calibrateCatalog().""" 

269 

270 # test calculations on a sourceCatalog, returning the array 

271 result = photoCalib.instFluxToNanojansky(catalog, self.instFluxKeyName) 

272 self.assertFloatsAlmostEqual(expectNanojansky, result) 

273 result = photoCalib.instFluxToMagnitude(catalog, self.instFluxKeyName) 

274 self.assertFloatsAlmostEqual(expectMag, result) 

275 

276 # Test modifying the catalog in-place with instFluxToNanojansky/instFluxToMagnitude 

277 # The original instFluxes shouldn't change: save them to test that. 

278 origInstFlux = catalog[self.instFluxKeyName+'_instFlux'].copy() 

279 origInstFluxErr = catalog[self.instFluxKeyName+'_instFluxErr'].copy() 

280 

281 def checkCatalog(catalog, expect, keyName, outField): 

282 """Test that the fields in the catalog are correct.""" 

283 self.assertFloatsAlmostEqual(catalog[keyName+'_%s' % outField], expect[:, 0]) 

284 self.assertFloatsAlmostEqual(catalog[keyName+'_%sErr' % outField], expect[:, 1]) 

285 self.assertFloatsAlmostEqual(catalog[keyName+'_instFlux'], origInstFlux) 

286 self.assertFloatsAlmostEqual(catalog[keyName+'_instFluxErr'], origInstFluxErr) 

287 

288 testCat = catalog.copy(deep=True) 

289 photoCalib.instFluxToMagnitude(testCat, self.instFluxKeyName, self.instFluxKeyName) 

290 checkCatalog(testCat, expectMag, self.instFluxKeyName, "mag") 

291 

292 testCat = catalog.copy(deep=True) 

293 photoCalib.instFluxToNanojansky(testCat, self.instFluxKeyName, self.instFluxKeyName) 

294 checkCatalog(testCat, expectNanojansky, self.instFluxKeyName, "flux") 

295 

296 testCat = catalog.copy(deep=True) 

297 photoCalib.instFluxToMagnitude(testCat, self.instFluxKeyName, self.instFluxKeyName) 

298 checkCatalog(testCat, expectMag, self.instFluxKeyName, "mag") 

299 

300 # test returning a calibrated catalog with calibrateCatalog 

301 

302 # test that trying to calibrate a non-existent flux field raises 

303 with self.assertRaises(lsst.pex.exceptions.NotFoundError): 

304 photoCalib.calibrateCatalog(testCat, ["NotARealFluxFieldName"]) 

305 

306 # test calibrating just one flux field 

307 testCat = catalog.copy(deep=True) 

308 result = photoCalib.calibrateCatalog(testCat, [self.otherInstFluxKeyName]) 

309 checkCatalog(result, expectNanojansky, self.otherInstFluxKeyName, "flux") 

310 checkCatalog(result, expectMag, self.otherInstFluxKeyName, "mag") 

311 

312 # test calibrating all of the flux fields 

313 testCat = catalog.copy(deep=True) 

314 result = photoCalib.calibrateCatalog(testCat) 

315 checkCatalog(result, expectNanojansky, self.instFluxKeyName, "flux") 

316 checkCatalog(result, expectMag, self.instFluxKeyName, "mag") 

317 checkCatalog(result, expectNanojansky, self.otherInstFluxKeyName, "flux") 

318 checkCatalog(result, expectMag, self.otherInstFluxKeyName, "mag") 

319 self.assertFloatsAlmostEqual(result[self.noErrInstFluxKeyName+'_flux'], expectNanojansky[:, 0]) 

320 self.assertFloatsAlmostEqual(result[self.noErrInstFluxKeyName+'_mag'], expectMag[:, 0]) 

321 self.assertFloatsAlmostEqual(result[self.noErrInstFluxKeyName+'_instFlux'], origInstFlux) 

322 

323 def testNonVarying(self): 

324 """Test constructing with a constant calibration factor.""" 

325 photoCalib = lsst.afw.image.PhotoCalib(self.calibration) 

326 self._testPhotoCalibCenter(photoCalib, 0) 

327 

328 # test on positions off the center (position should not matter) 

329 self.assertEqual(self.flux1, photoCalib.instFluxToNanojansky(self.instFlux1, self.pointXShift)) 

330 self.assertEqual(self.mag1, photoCalib.instFluxToMagnitude(self.instFlux1, self.pointXShift)) 

331 result = photoCalib.instFluxToNanojansky(self.instFlux1, self.instFluxErr1) 

332 self.assertEqual(self.flux1, result.value) 

333 

334 photoCalib = lsst.afw.image.PhotoCalib(self.calibration, self.calibrationErr) 

335 self._testPhotoCalibCenter(photoCalib, self.calibrationErr) 

336 

337 # test converting to a photoCalib 

338 photoCalib = lsst.afw.image.PhotoCalib(self.calibration, bbox=self.bbox) 

339 self._testPhotoCalibCenter(photoCalib, 0) 

340 

341 def testConstantBoundedField(self): 

342 """Test constructing with a spatially-constant bounded field.""" 

343 photoCalib = lsst.afw.image.PhotoCalib(self.constantCalibration) 

344 self._testPhotoCalibCenter(photoCalib, 0) 

345 

346 # test on positions off the center (position should not matter) 

347 self.assertEqual(self.flux1, photoCalib.instFluxToNanojansky(self.instFlux1, self.pointYShift)) 

348 self.assertEqual(self.mag1, photoCalib.instFluxToMagnitude(self.instFlux1, self.pointYShift)) 

349 self.assertFloatsAlmostEqual(self.flux2, 

350 photoCalib.instFluxToNanojansky(self.instFlux2, self.pointXShift)) 

351 self.assertFloatsAlmostEqual(self.mag2, 

352 photoCalib.instFluxToMagnitude(self.instFlux2, self.pointXShift)) 

353 

354 # test converting to a photoCalib 

355 photoCalib = lsst.afw.image.PhotoCalib(self.constantCalibration, self.calibrationErr) 

356 self._testPhotoCalibCenter(photoCalib, self.calibrationErr) 

357 

358 def testLinearXBoundedField(self): 

359 photoCalib = lsst.afw.image.PhotoCalib(self.linearXCalibration) 

360 self._testPhotoCalibCenter(photoCalib, 0) 

361 

362 # test on positions off the center (Y position should not matter) 

363 self.assertEqual(self.flux1, photoCalib.instFluxToNanojansky(self.instFlux1, self.pointYShift)) 

364 self.assertEqual(self.mag1, photoCalib.instFluxToMagnitude(self.instFlux1, self.pointYShift)) 

365 

366 # test on positions off the center (X position does matter) 

367 calibration = (self.calibration + self.pointXShift.getX()*self.calibration/(self.bbox.getWidth()/2.)) 

368 expect = self.instFlux1*calibration 

369 self.assertFloatsAlmostEqual(expect, 

370 photoCalib.instFluxToNanojansky(self.instFlux1, self.pointXShift)) 

371 self.assertFloatsAlmostEqual((expect*u.nJy).to_value(u.ABmag), 

372 photoCalib.instFluxToMagnitude(self.instFlux1, self.pointXShift)) 

373 expect2 = self.instFlux2*calibration 

374 self.assertFloatsAlmostEqual(expect2, 

375 photoCalib.instFluxToNanojansky(self.instFlux2, self.pointXShift)) 

376 self.assertFloatsAlmostEqual((expect2*u.nJy).to_value(u.ABmag), 

377 photoCalib.instFluxToMagnitude(self.instFlux2, self.pointXShift)) 

378 

379 # test converting to a photoCalib 

380 photoCalib = lsst.afw.image.PhotoCalib(self.linearXCalibration, self.calibrationErr) 

381 self._testPhotoCalibCenter(photoCalib, self.calibrationErr) 

382 

383 # New catalog with a spatial component in the varying direction, 

384 # to ensure the calculations on a catalog properly handle non-constant BF. 

385 # NOTE: only the first quantity of the result (nJy or mags) should change. 

386 catalog = self.catalog.copy(deep=True) 

387 catalog[0].set('centroid_x', self.pointXShift[0]) 

388 catalog[0].set('centroid_y', self.pointXShift[1]) 

389 errFlux1 = computeNanojanskyErr(self.instFluxErr1, 

390 self.instFlux1, 

391 self.calibrationErr, 

392 calibration, 

393 expect) 

394 errMag1 = computeMagnitudeErr(self.instFluxErr1, 

395 self.instFlux1, 

396 self.calibrationErr, 

397 calibration, 

398 expect) 

399 # re-use the same instFluxErr1 for instFlux2. 

400 errFlux2 = computeNanojanskyErr(self.instFluxErr1, 

401 self.instFlux2, 

402 self.calibrationErr, 

403 self.calibration, 

404 self.flux2) 

405 errMag2 = computeMagnitudeErr(self.instFluxErr1, 

406 self.instFlux2, 

407 self.calibrationErr, 

408 self.calibration, 

409 self.flux2) 

410 expectNanojansky = np.array([[expect, errFlux1], [self.flux2, errFlux2]]) 

411 expectMag = np.array([[(expect*u.nJy).to_value(u.ABmag), errMag1], [self.mag2, errMag2]]) 

412 self._testSourceCatalog(photoCalib, catalog, expectNanojansky, expectMag) 

413 

414 def testComputeScaledCalibration(self): 

415 photoCalib = lsst.afw.image.PhotoCalib(self.calibration, bbox=self.bbox) 

416 scaledCalib = lsst.afw.image.PhotoCalib(photoCalib.computeScaledCalibration()) 

417 self.assertEqual(1, scaledCalib.instFluxToNanojansky(self.instFlux1)*photoCalib.getCalibrationMean()) 

418 self.assertEqual(photoCalib.instFluxToNanojansky(self.instFlux1), 

419 scaledCalib.instFluxToNanojansky(self.instFlux1)*photoCalib.getCalibrationMean()) 

420 

421 photoCalib = lsst.afw.image.PhotoCalib(self.constantCalibration) 

422 scaledCalib = lsst.afw.image.PhotoCalib(photoCalib.computeScaledCalibration()) 

423 

424 self.assertEqual(1, scaledCalib.instFluxToNanojansky(self.instFlux1*self.calibration)) 

425 self.assertEqual(photoCalib.instFluxToNanojansky(self.instFlux1), 

426 scaledCalib.instFluxToNanojansky(self.instFlux1)*photoCalib.getCalibrationMean()) 

427 

428 @unittest.skip("Not yet implemented: see DM-10154") 

429 def testComputeScalingTo(self): 

430 photoCalib1 = lsst.afw.image.PhotoCalib(self.calibration, self.calibrationErr, bbox=self.bbox) 

431 photoCalib2 = lsst.afw.image.PhotoCalib(self.calibration*500, self.calibrationErr, bbox=self.bbox) 

432 scaling = photoCalib1.computeScalingTo(photoCalib2)(self.pointXShift) 

433 self.assertEqual(photoCalib1.instFluxToNanojansky(self.instFlux1, self.pointXShift)*scaling, 

434 photoCalib2.instFluxToNanojansky(self.instFlux1, self.pointXShift)) 

435 

436 photoCalib3 = lsst.afw.image.PhotoCalib(self.constantCalibration, self.calibrationErr) 

437 scaling = photoCalib1.computeScalingTo(photoCalib3)(self.pointXShift) 

438 self.assertEqual(photoCalib1.instFluxToNanojansky(self.instFlux1, self.pointXShift)*scaling, 

439 photoCalib3.instFluxToNanojansky(self.instFlux1, self.pointXShift)) 

440 scaling = photoCalib3.computeScalingTo(photoCalib1)(self.pointXShift) 

441 self.assertEqual(photoCalib3.instFluxToNanojansky(self.instFlux1, self.pointXShift)*scaling, 

442 photoCalib1.instFluxToNanojansky(self.instFlux1, self.pointXShift)) 

443 

444 photoCalib4 = lsst.afw.image.PhotoCalib(self.linearXCalibration, self.calibrationErr) 

445 scaling = photoCalib1.computeScalingTo(photoCalib4)(self.pointXShift) 

446 self.assertEqual(photoCalib1.instFluxToNanojansky(self.instFlux1, self.pointXShift)*scaling, 

447 photoCalib4.instFluxToNanojansky(self.instFlux1, self.pointXShift)) 

448 scaling = photoCalib4.computeScalingTo(photoCalib1)(self.pointXShift) 

449 self.assertEqual(photoCalib4.instFluxToNanojansky(self.instFlux1, self.pointXShift)*scaling, 

450 photoCalib1.instFluxToNanojansky(self.instFlux1, self.pointXShift)) 

451 

452 # Don't allow division of BoundedFields with different bounding boxes 

453 photoCalibNoBBox = lsst.afw.image.PhotoCalib(self.calibration, self.calibrationErr) 

454 with self.assertRaises(lsst.pex.exceptions.DomainError): 

455 scaling = photoCalibNoBBox.computeScalingTo(photoCalib1) 

456 with self.assertRaises(lsst.pex.exceptions.DomainError): 

457 scaling = photoCalibNoBBox.computeScalingTo(photoCalib4) 

458 with self.assertRaises(lsst.pex.exceptions.DomainError): 

459 scaling = photoCalib1.computeScalingTo(photoCalibNoBBox) 

460 

461 def _testPersistence(self, photoCalib): 

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

463 photoCalib.writeFits(filename) 

464 result = lsst.afw.image.PhotoCalib.readFits(filename) 

465 self.assertEqual(result, photoCalib) 

466 

467 def testPersistence(self): 

468 photoCalib = lsst.afw.image.PhotoCalib(self.calibration) 

469 self._testPersistence(photoCalib) 

470 

471 photoCalib = lsst.afw.image.PhotoCalib(self.calibration, self.calibrationErr) 

472 self._testPersistence(photoCalib) 

473 

474 photoCalib = lsst.afw.image.PhotoCalib(self.calibration, self.calibrationErr, self.bbox) 

475 self._testPersistence(photoCalib) 

476 

477 photoCalib = lsst.afw.image.PhotoCalib(self.constantCalibration, self.calibrationErr) 

478 self._testPersistence(photoCalib) 

479 

480 photoCalib = lsst.afw.image.PhotoCalib(self.linearXCalibration, self.calibrationErr) 

481 self._testPersistence(photoCalib) 

482 

483 def testPersistenceVersions(self): 

484 """Test that different versions are handled appropriately.""" 

485 # the values that were persisted in this photoCalib 

486 mean = 123 

487 err = 45 

488 dataDir = os.path.join(os.path.split(__file__)[0], "data") 

489 

490 # implicit version 0 should raise (no longer compatible) 

491 filePath = os.path.join(dataDir, "photoCalib-noversion.fits") 

492 with self.assertRaises(RuntimeError): 

493 photoCalib = lsst.afw.image.PhotoCalib.readFits(filePath) 

494 

495 # explicit version 0 should raise (no longer compatible) 

496 filePath = os.path.join(dataDir, "photoCalib-version0.fits") 

497 with self.assertRaises(RuntimeError): 

498 photoCalib = lsst.afw.image.PhotoCalib.readFits(filePath) 

499 

500 # explicit version 1 

501 filePath = os.path.join(dataDir, "photoCalib-version1.fits") 

502 photoCalib = lsst.afw.image.PhotoCalib.readFits(filePath) 

503 self.assertEqual(photoCalib.getCalibrationMean(), mean) 

504 self.assertEqual(photoCalib.getCalibrationErr(), err) 

505 

506 def testPhotoCalibEquality(self): 

507 photoCalib1 = lsst.afw.image.PhotoCalib(self.linearXCalibration, 0.5) 

508 photoCalib2 = lsst.afw.image.PhotoCalib(self.linearXCalibration, 0.5) 

509 photoCalib3 = lsst.afw.image.PhotoCalib(5, 0.5) 

510 photoCalib4 = lsst.afw.image.PhotoCalib(5, 0.5) 

511 photoCalib5 = lsst.afw.image.PhotoCalib(5) 

512 photoCalib6 = lsst.afw.image.PhotoCalib(self.linearXCalibration) 

513 photoCalib7 = lsst.afw.image.PhotoCalib(self.calibration, 0.5) 

514 photoCalib8 = lsst.afw.image.PhotoCalib(self.constantCalibration, 0.5) 

515 

516 constantCalibration = lsst.afw.math.ChebyshevBoundedField(self.bbox, np.array([[self.calibration]])) 

517 photoCalib9 = lsst.afw.image.PhotoCalib(constantCalibration, 0.5) 

518 

519 self.assertEqual(photoCalib1, photoCalib1) 

520 self.assertEqual(photoCalib1, photoCalib2) 

521 self.assertEqual(photoCalib3, photoCalib4) 

522 self.assertEqual(photoCalib8, photoCalib9) 

523 

524 self.assertNotEqual(photoCalib1, photoCalib6) 

525 self.assertNotEqual(photoCalib1, photoCalib7) 

526 self.assertNotEqual(photoCalib1, photoCalib3) 

527 self.assertNotEqual(photoCalib3, photoCalib5) 

528 self.assertNotEqual(photoCalib1, photoCalib8) 

529 

530 self.assertFalse(photoCalib1 != photoCalib2) # using assertFalse to directly test != operator 

531 

532 def setupImage(self): 

533 dim = (5, 6) 

534 npDim = (dim[1], dim[0]) # numpy and afw have a different x/y order 

535 sigma = 10.0 

536 image = np.random.normal(loc=1000.0, scale=sigma, size=npDim).astype(np.float32) 

537 mask = np.zeros(npDim, dtype=np.int32) 

538 variance = (np.random.normal(loc=0.0, scale=sigma, size=npDim).astype(np.float32))**2 

539 maskedImage = lsst.afw.image.basicUtils.makeMaskedImageFromArrays(image, mask, variance) 

540 maskedImage.mask[0, 0] = True # set one mask bit to check propagation of mask bits. 

541 

542 return npDim, maskedImage, image, mask, variance 

543 

544 def testCalibrateImageConstant(self): 

545 """Test a spatially-constant calibration.""" 

546 npDim, maskedImage, image, mask, variance = self.setupImage() 

547 outImage = maskedImage.image.getArray()*self.calibration 

548 expect = makeCalibratedMaskedImage(image, mask, variance, outImage, 

549 self.calibration, self.calibrationErr) 

550 photoCalib = lsst.afw.image.PhotoCalib(self.calibration, self.calibrationErr) 

551 result = photoCalib.calibrateImage(maskedImage) 

552 self.assertMaskedImagesAlmostEqual(expect, result) 

553 

554 # same test, but without using the calibration error 

555 expect = makeCalibratedMaskedImageNoCalibrationError(image, mask, variance, outImage) 

556 result = photoCalib.calibrateImage(maskedImage, includeScaleUncertainty=False) 

557 self.assertMaskedImagesAlmostEqual(expect, result) 

558 

559 def testCalibrateImageNonConstant(self): 

560 """Test a spatially-varying calibration.""" 

561 npDim, maskedImage, image, mask, variance = self.setupImage() 

562 xIndex, yIndex = np.indices(npDim, dtype=np.float64) 

563 # y then x, as afw order and np order are flipped 

564 calibration = self.linearXCalibration.evaluate(yIndex.flatten(), xIndex.flatten()).reshape(npDim) 

565 outImage = maskedImage.image.getArray()*calibration # element-wise product, not matrix product 

566 expect = makeCalibratedMaskedImage(image, mask, variance, outImage, calibration, self.calibrationErr) 

567 

568 photoCalib = lsst.afw.image.PhotoCalib(self.linearXCalibration, self.calibrationErr) 

569 result = photoCalib.calibrateImage(maskedImage) 

570 self.assertMaskedImagesAlmostEqual(expect, result) 

571 

572 # same test, but without using the calibration error 

573 expect = makeCalibratedMaskedImageNoCalibrationError(image, mask, variance, outImage) 

574 result = photoCalib.calibrateImage(maskedImage, includeScaleUncertainty=False) 

575 self.assertMaskedImagesAlmostEqual(expect, result) 

576 

577 def testCalibrateImageNonConstantSubimage(self): 

578 """Test a non-constant calibration on a sub-image, to ensure we're 

579 handling xy0 correctly. 

580 """ 

581 npDim, maskedImage, image, mask, variance = self.setupImage() 

582 xIndex, yIndex = np.indices(npDim, dtype=np.float64) 

583 calibration = self.linearXCalibration.evaluate(yIndex.flatten(), xIndex.flatten()).reshape(npDim) 

584 

585 outImage = maskedImage.image.getArray()*calibration # element-wise product, not matrix product 

586 expect = makeCalibratedMaskedImage(image, mask, variance, outImage, calibration, self.calibrationErr) 

587 

588 subBox = lsst.geom.Box2I(lsst.geom.Point2I(2, 4), lsst.geom.Point2I(4, 5)) 

589 subImage = maskedImage.subset(subBox) 

590 photoCalib = lsst.afw.image.PhotoCalib(self.linearXCalibration, self.calibrationErr) 

591 result = photoCalib.calibrateImage(subImage) 

592 self.assertMaskedImagesAlmostEqual(expect.subset(subBox), result) 

593 

594 # same test, but without using the calibration error 

595 expect = makeCalibratedMaskedImageNoCalibrationError(image, mask, variance, outImage) 

596 result = photoCalib.calibrateImage(maskedImage, includeScaleUncertainty=False) 

597 self.assertMaskedImagesAlmostEqual(expect, result) 

598 

599 def testNonPositiveMeans(self): 

600 # no negative calibrations 

601 with(self.assertRaises(lsst.pex.exceptions.InvalidParameterError)): 

602 lsst.afw.image.PhotoCalib(-1.0) 

603 # no negative errors 

604 with(self.assertRaises(lsst.pex.exceptions.InvalidParameterError)): 

605 lsst.afw.image.PhotoCalib(1.0, -1.0) 

606 

607 # no negative calibration mean when computed from the bounded field 

608 negativeCalibration = lsst.afw.math.ChebyshevBoundedField(self.bbox, 

609 np.array([[-self.calibration]])) 

610 with(self.assertRaises(lsst.pex.exceptions.InvalidParameterError)): 

611 lsst.afw.image.PhotoCalib(negativeCalibration) 

612 # no negative calibration error 

613 with(self.assertRaises(lsst.pex.exceptions.InvalidParameterError)): 

614 lsst.afw.image.PhotoCalib(self.constantCalibration, -1.0) 

615 

616 # no negative explicit calibration mean 

617 with(self.assertRaises(lsst.pex.exceptions.InvalidParameterError)): 

618 lsst.afw.image.PhotoCalib(-1.0, 0, self.constantCalibration, True) 

619 # no negative calibration error 

620 with(self.assertRaises(lsst.pex.exceptions.InvalidParameterError)): 

621 lsst.afw.image.PhotoCalib(1.0, -1.0, self.constantCalibration, True) 

622 

623 def testPositiveErrors(self): 

624 """The errors should always be positive, regardless of whether the 

625 input flux is negative (as can happen in difference imaging). 

626 This tests and fixes tickets/DM-16696. 

627 """ 

628 photoCalib = lsst.afw.image.PhotoCalib(self.calibration) 

629 result = photoCalib.instFluxToNanojansky(-100, 10) 

630 self.assertGreater(result.error, 0) 

631 

632 def testMakePhotoCalibFromMetadata(self): 

633 """Test creating a PhotoCalib from the Calib FITS metadata. 

634 """ 

635 fluxMag0 = 12345 

636 metadata = lsst.daf.base.PropertySet() 

637 metadata.set('FLUXMAG0', fluxMag0) 

638 

639 photoCalib = lsst.afw.image.makePhotoCalibFromMetadata(metadata) 

640 self.assertEqual(photoCalib.getInstFluxAtZeroMagnitude(), fluxMag0) 

641 self.assertEqual(photoCalib.getCalibrationErr(), 0.0) 

642 # keys aren't deleted by default 

643 self.assertIn('FLUXMAG0', metadata) 

644 

645 # Test reading with the error keyword 

646 fluxMag0Err = 6789 

647 metadata.set('FLUXMAG0ERR', fluxMag0Err) 

648 # The reference flux is "nanoJanskys at 0 magnitude". 

649 referenceFlux = (0*u.ABmag).to_value(u.nJy) 

650 calibrationErr = referenceFlux*fluxMag0Err/fluxMag0**2 

651 photoCalib = lsst.afw.image.makePhotoCalibFromMetadata(metadata) 

652 self.assertEqual(photoCalib.getInstFluxAtZeroMagnitude(), fluxMag0) 

653 self.assertFloatsAlmostEqual(photoCalib.getCalibrationErr(), calibrationErr) 

654 # keys aren't deleted by default 

655 self.assertIn('FLUXMAG0', metadata) 

656 self.assertIn('FLUXMAG0ERR', metadata) 

657 

658 # test stripping keys from a new metadata 

659 metadata = lsst.daf.base.PropertySet() 

660 metadata.set('FLUXMAG0', fluxMag0) 

661 photoCalib = lsst.afw.image.makePhotoCalibFromMetadata(metadata, strip=True) 

662 self.assertEqual(photoCalib.getInstFluxAtZeroMagnitude(), fluxMag0) 

663 self.assertEqual(photoCalib.getCalibrationErr(), 0.0) 

664 self.assertNotIn('FLUXMAG0', metadata) 

665 

666 metadata.set('FLUXMAG0', fluxMag0) 

667 metadata.set('FLUXMAG0ERR', fluxMag0Err) 

668 photoCalib = lsst.afw.image.makePhotoCalibFromMetadata(metadata, strip=True) 

669 self.assertEqual(photoCalib.getInstFluxAtZeroMagnitude(), fluxMag0) 

670 self.assertFloatsAlmostEqual(photoCalib.getCalibrationErr(), calibrationErr) 

671 self.assertNotIn('FLUXMAG0', metadata) 

672 self.assertNotIn('FLUXMAG0ERR', metadata) 

673 

674 def testMakePhotoCalibFromMetadataNoKey(self): 

675 """Return None if the metadata does not contain a 'FLUXMAG0' key.""" 

676 metadata = lsst.daf.base.PropertySet() 

677 metadata.set('something', 1000) 

678 metadata.set('FLUXMAG0ERR', 5) 

679 result = lsst.afw.image.makePhotoCalibFromMetadata(metadata) 

680 self.assertIsNone(result) 

681 

682 def testMakePhotoCalibFromCalibZeroPoint(self): 

683 """Test creating from the Calib-style fluxMag0/fluxMag0Err values.""" 

684 fluxMag0 = 12345 

685 fluxMag0Err = 67890 

686 

687 referenceFlux = (0*u.ABmag).to_value(u.nJy) 

688 calibrationErr = referenceFlux*fluxMag0Err/fluxMag0**2 

689 

690 # create with all zeros 

691 photoCalib = lsst.afw.image.makePhotoCalibFromCalibZeroPoint(0, 0) 

692 self.assertEqual(photoCalib.getInstFluxAtZeroMagnitude(), 0) 

693 self.assertEqual(photoCalib.getCalibrationMean(), np.inf) 

694 self.assertTrue(np.isnan(photoCalib.getCalibrationErr())) 

695 

696 # create with non-zero fluxMag0, but zero err 

697 photoCalib = lsst.afw.image.makePhotoCalibFromCalibZeroPoint(fluxMag0, 0) 

698 self.assertEqual(photoCalib.getInstFluxAtZeroMagnitude(), fluxMag0) 

699 self.assertEqual(photoCalib.getCalibrationErr(), 0.0) 

700 

701 # create with non-zero fluxMag0 and err 

702 photoCalib = lsst.afw.image.makePhotoCalibFromCalibZeroPoint(fluxMag0, fluxMag0Err) 

703 self.assertEqual(photoCalib.getInstFluxAtZeroMagnitude(), fluxMag0) 

704 self.assertFloatsAlmostEqual(photoCalib.getCalibrationErr(), calibrationErr) 

705 

706 

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

708 pass 

709 

710 

711def setup_module(module): 

712 lsst.utils.tests.init() 

713 

714 

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

716 lsst.utils.tests.init() 

717 unittest.main()