Coverage for tests/test_functors.py: 12%

382 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-19 05:10 -0700

1# This file is part of pipe_tasks. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (http://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 <http://www.gnu.org/licenses/>. 

21 

22import astropy.units as u 

23import copy 

24import functools 

25import numpy as np 

26import os 

27import pandas as pd 

28import unittest 

29import logging 

30 

31import lsst.daf.base as dafBase 

32import lsst.afw.geom as afwGeom 

33import lsst.geom as geom 

34from lsst.sphgeom import HtmPixelization 

35import lsst.meas.base as measBase 

36import lsst.utils.tests 

37from lsst.pipe.base import InMemoryDatasetHandle 

38from lsst.pipe.tasks.functors import (CompositeFunctor, CustomFunctor, Column, RAColumn, 

39 DecColumn, Mag, MagDiff, Color, 

40 DeconvolvedMoments, SdssTraceSize, PsfSdssTraceSizeDiff, 

41 HsmTraceSize, PsfHsmTraceSizeDiff, HsmFwhm, 

42 LocalPhotometry, LocalNanojansky, LocalNanojanskyErr, 

43 LocalDipoleMeanFlux, LocalDipoleMeanFluxErr, 

44 LocalDipoleDiffFlux, LocalDipoleDiffFluxErr, 

45 LocalWcs, ComputePixelScale, ConvertPixelToArcseconds, 

46 ConvertPixelSqToArcsecondsSq, HtmIndex20, Ebv) 

47 

48ROOT = os.path.abspath(os.path.dirname(__file__)) 

49 

50 

51class FunctorTestCase(unittest.TestCase): 

52 

53 def getMultiIndexDataFrame(self, dataDict): 

54 """Create a simple test multi-index DataFrame.""" 

55 

56 simpleDF = pd.DataFrame(dataDict) 

57 dfFilterDSCombos = [] 

58 for ds in self.datasets: 

59 for band in self.bands: 

60 df = copy.copy(simpleDF) 

61 df.reindex(sorted(df.columns), axis=1) 

62 df['dataset'] = ds 

63 df['band'] = band 

64 df.columns = pd.MultiIndex.from_tuples( 

65 [(ds, band, c) for c in df.columns], 

66 names=('dataset', 'band', 'column')) 

67 dfFilterDSCombos.append(df) 

68 

69 df = functools.reduce(lambda d1, d2: d1.join(d2), dfFilterDSCombos) 

70 

71 return df 

72 

73 def getSimpleDataFrame(self, dataDict): 

74 return pd.DataFrame(dataDict) 

75 

76 def getDatasetHandle(self, df): 

77 lo, hi = HtmPixelization(7).universe().ranges()[0] 

78 value = np.random.randint(lo, hi) 

79 return InMemoryDatasetHandle(df, storageClass="DataFrame", dataId={"htm7": value}) 

80 

81 def setUp(self): 

82 np.random.seed(12345) 

83 self.datasets = ['forced_src', 'meas', 'ref'] 

84 self.bands = ['g', 'r'] 

85 self.columns = ['coord_ra', 'coord_dec'] 

86 self.nRecords = 5 

87 self.dataDict = { 

88 "coord_ra": [3.77654137, 3.77643059, 3.77621148, 3.77611944, 3.77610396], 

89 "coord_dec": [0.01127624, 0.01127787, 0.01127543, 0.01127543, 0.01127543]} 

90 

91 def _funcVal(self, functor, df): 

92 self.assertIsInstance(functor.name, str) 

93 self.assertIsInstance(functor.shortname, str) 

94 

95 handle = self.getDatasetHandle(df) 

96 

97 val = functor(df) 

98 val2 = functor(handle) 

99 self.assertTrue((val == val2).all()) 

100 self.assertIsInstance(val, pd.Series) 

101 

102 val = functor(df, dropna=True) 

103 val2 = functor(handle, dropna=True) 

104 self.assertTrue((val == val2).all()) 

105 self.assertEqual(val.isnull().sum(), 0) 

106 

107 return val 

108 

109 def _differenceVal(self, functor, df1, df2): 

110 self.assertIsInstance(functor.name, str) 

111 self.assertIsInstance(functor.shortname, str) 

112 

113 handle1 = self.getDatasetHandle(df1) 

114 handle2 = self.getDatasetHandle(df2) 

115 

116 val = functor.difference(df1, df2) 

117 val2 = functor.difference(handle1, handle2) 

118 self.assertTrue(val.equals(val2)) 

119 self.assertIsInstance(val, pd.Series) 

120 

121 val = functor.difference(df1, df2, dropna=True) 

122 val2 = functor.difference(handle1, handle2, dropna=True) 

123 self.assertTrue(val.equals(val2)) 

124 self.assertEqual(val.isnull().sum(), 0) 

125 

126 val1 = self._funcVal(functor, df1) 

127 val2 = self._funcVal(functor, df2) 

128 

129 self.assertTrue(np.allclose(val, val1 - val2)) 

130 

131 return val 

132 

133 def testColumn(self): 

134 self.columns.append("base_FootprintArea_value") 

135 self.dataDict["base_FootprintArea_value"] = \ 

136 np.full(self.nRecords, 1) 

137 df = self.getMultiIndexDataFrame(self.dataDict) 

138 func = Column('base_FootprintArea_value', filt='g') 

139 self._funcVal(func, df) 

140 

141 df = self.getSimpleDataFrame(self.dataDict) 

142 func = Column('base_FootprintArea_value') 

143 self._funcVal(func, df) 

144 

145 def testCustom(self): 

146 self.columns.append("base_FootprintArea_value") 

147 self.dataDict["base_FootprintArea_value"] = \ 

148 np.random.rand(self.nRecords) 

149 df = self.getMultiIndexDataFrame(self.dataDict) 

150 func = CustomFunctor('2*base_FootprintArea_value', filt='g') 

151 val = self._funcVal(func, df) 

152 

153 func2 = Column('base_FootprintArea_value', filt='g') 

154 

155 np.allclose(val.values, 2*func2(df).values, atol=1e-13, rtol=0) 

156 

157 df = self.getSimpleDataFrame(self.dataDict) 

158 func = CustomFunctor('2 * base_FootprintArea_value') 

159 val = self._funcVal(func, df) 

160 func2 = Column('base_FootprintArea_value') 

161 

162 np.allclose(val.values, 2*func2(df).values, atol=1e-13, rtol=0) 

163 

164 def testCoords(self): 

165 df = self.getMultiIndexDataFrame(self.dataDict) 

166 ra = self._funcVal(RAColumn(), df) 

167 dec = self._funcVal(DecColumn(), df) 

168 

169 columnDict = {'dataset': 'ref', 'band': 'g', 

170 'column': ['coord_ra', 'coord_dec']} 

171 

172 handle = InMemoryDatasetHandle(df, storageClass="DataFrame") 

173 dfSub = handle.get(parameters={"columns": columnDict}) 

174 self._dropLevels(dfSub) 

175 

176 coords = dfSub / np.pi * 180. 

177 

178 self.assertTrue(np.allclose(ra, coords[('ref', 'g', 'coord_ra')], atol=1e-13, rtol=0)) 

179 self.assertTrue(np.allclose(dec, coords[('ref', 'g', 'coord_dec')], atol=1e-13, rtol=0)) 

180 

181 # single-level column index table 

182 df = self.getSimpleDataFrame(self.dataDict) 

183 ra = self._funcVal(RAColumn(), df) 

184 dec = self._funcVal(DecColumn(), df) 

185 

186 handle = InMemoryDatasetHandle(df, storageClass="DataFrame") 

187 dfSub = handle.get(parameters={"columns": ['coord_ra', 'coord_dec']}) 

188 coords = dfSub / np.pi * 180. 

189 

190 self.assertTrue(np.allclose(ra, coords['coord_ra'], atol=1e-13, rtol=0)) 

191 self.assertTrue(np.allclose(dec, coords['coord_dec'], atol=1e-13, rtol=0)) 

192 

193 def testMag(self): 

194 self.columns.extend(["base_PsfFlux_instFlux", "base_PsfFlux_instFluxErr"]) 

195 self.dataDict["base_PsfFlux_instFlux"] = np.full(self.nRecords, 1000) 

196 self.dataDict["base_PsfFlux_instFluxErr"] = np.full(self.nRecords, 10) 

197 df = self.getMultiIndexDataFrame(self.dataDict) 

198 # Change one dataset filter combinations value. 

199 df[("meas", "g", "base_PsfFlux_instFlux")] -= 1 

200 

201 fluxName = 'base_PsfFlux' 

202 

203 # Check that things work when you provide dataset explicitly 

204 for dataset in ['forced_src', 'meas']: 

205 psfMag_G = self._funcVal(Mag(fluxName, dataset=dataset, 

206 filt='g'), 

207 df) 

208 psfMag_R = self._funcVal(Mag(fluxName, dataset=dataset, 

209 filt='r'), 

210 df) 

211 

212 psfColor_GR = self._funcVal(Color(fluxName, 'g', 'r', 

213 dataset=dataset), 

214 df) 

215 

216 self.assertTrue(np.allclose((psfMag_G - psfMag_R).dropna(), psfColor_GR, rtol=0, atol=1e-13)) 

217 

218 # Check that behavior as expected when dataset not provided; 

219 # that is, that the color comes from forced and default Mag is meas 

220 psfMag_G = self._funcVal(Mag(fluxName, filt='g'), df) 

221 psfMag_R = self._funcVal(Mag(fluxName, filt='r'), df) 

222 

223 psfColor_GR = self._funcVal(Color(fluxName, 'g', 'r'), df) 

224 

225 # These should *not* be equal. 

226 self.assertFalse(np.allclose((psfMag_G - psfMag_R).dropna(), psfColor_GR)) 

227 

228 def testMagDiff(self): 

229 self.columns.extend(["base_PsfFlux_instFlux", "base_PsfFlux_instFluxErr", 

230 "modelfit_CModel_instFlux", "modelfit_CModel_instFluxErr"]) 

231 self.dataDict["base_PsfFlux_instFlux"] = np.full(self.nRecords, 1000) 

232 self.dataDict["base_PsfFlux_instFluxErr"] = np.full(self.nRecords, 10) 

233 self.dataDict["modelfit_CModel_instFlux"] = np.full(self.nRecords, 1000) 

234 self.dataDict["modelfit_CModel_instFluxErr"] = np.full(self.nRecords, 10) 

235 df = self.getMultiIndexDataFrame(self.dataDict) 

236 

237 for filt in self.bands: 

238 filt = 'g' 

239 val = self._funcVal(MagDiff('base_PsfFlux', 'modelfit_CModel', filt=filt), df) 

240 

241 mag1 = self._funcVal(Mag('modelfit_CModel', filt=filt), df) 

242 mag2 = self._funcVal(Mag('base_PsfFlux', filt=filt), df) 

243 self.assertTrue(np.allclose((mag2 - mag1).dropna(), val, rtol=0, atol=1e-13)) 

244 

245 def testDifference(self): 

246 """Test .difference method using MagDiff as the example. 

247 """ 

248 self.columns.extend(["base_PsfFlux_instFlux", "base_PsfFlux_instFluxErr", 

249 "modelfit_CModel_instFlux", "modelfit_CModel_instFluxErr"]) 

250 

251 self.dataDict["base_PsfFlux_instFlux"] = np.full(self.nRecords, 1000) 

252 self.dataDict["modelfit_CModel_instFlux"] = np.full(self.nRecords, 1000) 

253 df1 = self.getMultiIndexDataFrame(self.dataDict) 

254 

255 self.dataDict["base_PsfFlux_instFlux"] = np.full(self.nRecords, 999) 

256 self.dataDict["modelfit_CModel_instFlux"] = np.full(self.nRecords, 999) 

257 df2 = self.getMultiIndexDataFrame(self.dataDict) 

258 

259 magDiff = MagDiff('base_PsfFlux', 'modelfit_CModel', filt='g') 

260 

261 # Asserts that differences computed properly 

262 self._differenceVal(magDiff, df1, df2) 

263 

264 def testPixelScale(self): 

265 # Test that the pixel scale and pix->arcsec calculations perform as 

266 # expected. 

267 pass 

268 

269 def testOther(self): 

270 self.columns.extend(["ext_shapeHSM_HsmSourceMoments_xx", "ext_shapeHSM_HsmSourceMoments_yy", 

271 "base_SdssShape_xx", "base_SdssShape_yy", 

272 "ext_shapeHSM_HsmPsfMoments_xx", "ext_shapeHSM_HsmPsfMoments_yy", 

273 "base_SdssShape_psf_xx", "base_SdssShape_psf_yy"]) 

274 self.dataDict["ext_shapeHSM_HsmSourceMoments_xx"] = np.full(self.nRecords, 1 / np.sqrt(2)) 

275 self.dataDict["ext_shapeHSM_HsmSourceMoments_yy"] = np.full(self.nRecords, 1 / np.sqrt(2)) 

276 self.dataDict["base_SdssShape_xx"] = np.full(self.nRecords, 1 / np.sqrt(2)) 

277 self.dataDict["base_SdssShape_yy"] = np.full(self.nRecords, 1 / np.sqrt(2)) 

278 self.dataDict["ext_shapeHSM_HsmPsfMoments_xx"] = np.full(self.nRecords, 1 / np.sqrt(2)) 

279 self.dataDict["ext_shapeHSM_HsmPsfMoments_yy"] = np.full(self.nRecords, 1 / np.sqrt(2)) 

280 self.dataDict["base_SdssShape_psf_xx"] = np.full(self.nRecords, 1) 

281 self.dataDict["base_SdssShape_psf_yy"] = np.full(self.nRecords, 1) 

282 df = self.getMultiIndexDataFrame(self.dataDict) 

283 # Covering the code is better than nothing 

284 for filt in self.bands: 

285 for Func in [DeconvolvedMoments, 

286 SdssTraceSize, 

287 PsfSdssTraceSizeDiff, 

288 HsmTraceSize, PsfHsmTraceSizeDiff, HsmFwhm]: 

289 _ = self._funcVal(Func(filt=filt), df) 

290 

291 def _compositeFuncVal(self, functor, df): 

292 self.assertIsInstance(functor, CompositeFunctor) 

293 

294 handle = self.getDatasetHandle(df) 

295 

296 fdf1 = functor(df) 

297 fdf2 = functor(handle) 

298 self.assertTrue(fdf1.equals(fdf2)) 

299 

300 self.assertIsInstance(fdf1, pd.DataFrame) 

301 self.assertTrue(np.all([k in fdf1.columns for k in functor.funcDict.keys()])) 

302 

303 fdf1 = functor(df, dropna=True) 

304 fdf2 = functor(handle, dropna=True) 

305 self.assertTrue(fdf1.equals(fdf2)) 

306 

307 # Check that there are no nulls 

308 self.assertFalse(fdf1.isnull().any(axis=None)) 

309 

310 return fdf1 

311 

312 def _compositeDifferenceVal(self, functor, df1, df2): 

313 self.assertIsInstance(functor, CompositeFunctor) 

314 

315 handle1 = self.getDatasetHandle(df1) 

316 handle2 = self.getDatasetHandle(df2) 

317 

318 fdf1 = functor.difference(df1, df2) 

319 fdf2 = functor.difference(handle1, handle2) 

320 self.assertTrue(fdf1.equals(fdf2)) 

321 

322 self.assertIsInstance(fdf1, pd.DataFrame) 

323 self.assertTrue(np.all([k in fdf1.columns for k in functor.funcDict.keys()])) 

324 

325 fdf1 = functor.difference(df1, df2, dropna=True) 

326 fdf2 = functor.difference(handle1, handle2, dropna=True) 

327 self.assertTrue(fdf1.equals(fdf2)) 

328 

329 # Check that there are no nulls 

330 self.assertFalse(fdf1.isnull().any(axis=None)) 

331 

332 df1_functored = functor(df1) 

333 df2_functored = functor(df2) 

334 

335 self.assertTrue(np.allclose(fdf1.values, df1_functored.values - df2_functored.values)) 

336 

337 return fdf1 

338 

339 def testComposite(self): 

340 self.columns.extend(["modelfit_CModel_instFlux", "base_PsfFlux_instFlux"]) 

341 self.dataDict["modelfit_CModel_instFlux"] = np.full(self.nRecords, 1) 

342 self.dataDict["base_PsfFlux_instFlux"] = np.full(self.nRecords, 1) 

343 

344 df = self.getMultiIndexDataFrame(self.dataDict) 

345 # Modify r band value slightly. 

346 df[("meas", "r", "base_PsfFlux_instFlux")] -= 0.1 

347 

348 filt = 'g' 

349 funcDict = {'psfMag_ref': Mag('base_PsfFlux', dataset='ref'), 

350 'ra': RAColumn(), 

351 'dec': DecColumn(), 

352 'psfMag': Mag('base_PsfFlux', filt=filt), 

353 'cmodel_magDiff': MagDiff('base_PsfFlux', 

354 'modelfit_CModel', filt=filt)} 

355 func = CompositeFunctor(funcDict) 

356 fdf1 = self._compositeFuncVal(func, df) 

357 

358 # Repeat same, but define filter globally instead of individually 

359 funcDict2 = {'psfMag_ref': Mag('base_PsfFlux', dataset='ref'), 

360 'ra': RAColumn(), 

361 'dec': DecColumn(), 

362 'psfMag': Mag('base_PsfFlux'), 

363 'cmodel_magDiff': MagDiff('base_PsfFlux', 

364 'modelfit_CModel')} 

365 

366 func2 = CompositeFunctor(funcDict2, filt=filt) 

367 fdf2 = self._compositeFuncVal(func2, df) 

368 self.assertTrue(fdf1.equals(fdf2)) 

369 

370 func2.filt = 'r' 

371 fdf3 = self._compositeFuncVal(func2, df) 

372 # Because we modified the R filter this should fail. 

373 self.assertFalse(fdf2.equals(fdf3)) 

374 

375 # Make sure things work with passing list instead of dict 

376 funcs = [Mag('base_PsfFlux', dataset='ref'), 

377 RAColumn(), 

378 DecColumn(), 

379 Mag('base_PsfFlux', filt=filt), 

380 MagDiff('base_PsfFlux', 'modelfit_CModel', filt=filt)] 

381 

382 _ = self._compositeFuncVal(CompositeFunctor(funcs), df) 

383 

384 def testCompositeSimple(self): 

385 """Test single-level composite functor for functionality 

386 """ 

387 self.columns.extend(["modelfit_CModel_instFlux", "base_PsfFlux_instFlux"]) 

388 self.dataDict["modelfit_CModel_instFlux"] = np.full(self.nRecords, 1) 

389 self.dataDict["base_PsfFlux_instFlux"] = np.full(self.nRecords, 1) 

390 

391 df = self.getSimpleDataFrame(self.dataDict) 

392 funcDict = {'ra': RAColumn(), 

393 'dec': DecColumn(), 

394 'psfMag': Mag('base_PsfFlux'), 

395 'cmodel_magDiff': MagDiff('base_PsfFlux', 

396 'modelfit_CModel')} 

397 func = CompositeFunctor(funcDict) 

398 _ = self._compositeFuncVal(func, df) 

399 

400 def testCompositeColor(self): 

401 self.dataDict["base_PsfFlux_instFlux"] = np.full(self.nRecords, 1000) 

402 self.dataDict["base_PsfFlux_instFluxErr"] = np.full(self.nRecords, 10) 

403 df = self.getMultiIndexDataFrame(self.dataDict) 

404 funcDict = {'a': Mag('base_PsfFlux', dataset='meas', filt='g'), 

405 'b': Mag('base_PsfFlux', dataset='forced_src', filt='g'), 

406 'c': Color('base_PsfFlux', 'g', 'r')} 

407 # Covering the code is better than nothing 

408 _ = self._compositeFuncVal(CompositeFunctor(funcDict), df) 

409 

410 def testCompositeDifference(self): 

411 self.dataDict["base_PsfFlux_instFlux"] = np.full(self.nRecords, 1000) 

412 self.dataDict["base_PsfFlux_instFluxErr"] = np.full(self.nRecords, 10) 

413 df1 = self.getMultiIndexDataFrame(self.dataDict) 

414 

415 self.dataDict["base_PsfFlux_instFlux"] = np.full(self.nRecords, 999) 

416 self.dataDict["base_PsfFlux_instFluxErr"] = np.full(self.nRecords, 9) 

417 df2 = self.getMultiIndexDataFrame(self.dataDict) 

418 

419 funcDict = {'a': Mag('base_PsfFlux', dataset='meas', filt='g'), 

420 'b': Mag('base_PsfFlux', dataset='forced_src', filt='g'), 

421 'c': Color('base_PsfFlux', 'g', 'r')} 

422 # Covering the code is better than nothing 

423 _ = self._compositeDifferenceVal(CompositeFunctor(funcDict), df1, df2) 

424 

425 def testCompositeFail(self): 

426 """Test a composite functor where one of the functors should be junk. 

427 """ 

428 self.dataDict["base_PsfFlux_instFlux"] = np.full(self.nRecords, 1000) 

429 df = self.getMultiIndexDataFrame(self.dataDict) 

430 

431 funcDict = {'good': Column("base_PsfFlux_instFlux"), 

432 'bad': Column('not_a_column')} 

433 

434 with self.assertLogs(level=logging.ERROR) as cm: 

435 _ = self._compositeFuncVal(CompositeFunctor(funcDict), df) 

436 self.assertIn("Exception in CompositeFunctor (funcs: ['good', 'bad'])", cm.output[0]) 

437 

438 def testLocalPhotometry(self): 

439 """Test the local photometry functors. 

440 """ 

441 flux = 1000 

442 fluxErr = 10 

443 calib = 10 

444 calibErr = 1 

445 self.dataDict["base_PsfFlux_instFlux"] = np.full(self.nRecords, flux) 

446 self.dataDict["base_PsfFlux_instFluxErr"] = np.full(self.nRecords, 

447 fluxErr) 

448 self.dataDict["base_LocalPhotoCalib"] = np.full(self.nRecords, calib) 

449 self.dataDict["base_LocalPhotoCalibErr"] = np.full(self.nRecords, 

450 calibErr) 

451 df = self.getMultiIndexDataFrame(self.dataDict) 

452 func = LocalPhotometry("base_PsfFlux_instFlux", 

453 "base_PsfFlux_instFluxErr", 

454 "base_LocalPhotoCalib", 

455 "base_LocalPhotoCalibErr") 

456 

457 nanoJansky = func.instFluxToNanojansky( 

458 df[("meas", "g", "base_PsfFlux_instFlux")], 

459 df[("meas", "g", "base_LocalPhotoCalib")]) 

460 mag = func.instFluxToMagnitude( 

461 df[("meas", "g", "base_PsfFlux_instFlux")], 

462 df[("meas", "g", "base_LocalPhotoCalib")]) 

463 nanoJanskyErr = func.instFluxErrToNanojanskyErr( 

464 df[("meas", "g", "base_PsfFlux_instFlux")], 

465 df[("meas", "g", "base_PsfFlux_instFluxErr")], 

466 df[("meas", "g", "base_LocalPhotoCalib")], 

467 df[("meas", "g", "base_LocalPhotoCalibErr")]) 

468 magErr = func.instFluxErrToMagnitudeErr( 

469 df[("meas", "g", "base_PsfFlux_instFlux")], 

470 df[("meas", "g", "base_PsfFlux_instFluxErr")], 

471 df[("meas", "g", "base_LocalPhotoCalib")], 

472 df[("meas", "g", "base_LocalPhotoCalibErr")]) 

473 

474 self.assertTrue(np.allclose(nanoJansky.values, 

475 flux * calib, 

476 atol=1e-13, 

477 rtol=0)) 

478 self.assertTrue(np.allclose(mag.values, 

479 (flux * calib * u.nJy).to_value(u.ABmag), 

480 atol=1e-13, 

481 rtol=0)) 

482 self.assertTrue(np.allclose(nanoJanskyErr.values, 

483 np.hypot(fluxErr * calib, flux * calibErr), 

484 atol=1e-13, 

485 rtol=0)) 

486 self.assertTrue(np.allclose( 

487 magErr.values, 

488 2.5 / np.log(10) * nanoJanskyErr.values / nanoJansky.values, 

489 atol=1e-13, 

490 rtol=0)) 

491 

492 # Test functors against the values computed above. 

493 self._testLocalPhotometryFunctors(LocalNanojansky, 

494 df, 

495 nanoJansky) 

496 self._testLocalPhotometryFunctors(LocalNanojanskyErr, 

497 df, 

498 nanoJanskyErr) 

499 

500 def _testLocalPhotometryFunctors(self, functor, df, testValues): 

501 func = functor("base_PsfFlux_instFlux", 

502 "base_PsfFlux_instFluxErr", 

503 "base_LocalPhotoCalib", 

504 "base_LocalPhotoCalibErr") 

505 val = self._funcVal(func, df) 

506 self.assertTrue(np.allclose(testValues.values, 

507 val.values, 

508 atol=1e-13, 

509 rtol=0)) 

510 

511 def testDipPhotometry(self): 

512 """Test calibrated flux calculations for dipoles.""" 

513 fluxNeg = -100 

514 fluxPos = 101 

515 fluxErr = 10 

516 calib = 10 

517 calibErr = 1 

518 

519 # compute expected values. 

520 absMean = 0.5*(fluxPos - fluxNeg)*calib 

521 absDiff = (fluxNeg + fluxPos)*calib 

522 absMeanErr = 0.5*np.sqrt(2*(fluxErr*calib)**2 

523 + ((fluxPos - fluxNeg)*calibErr)**2) 

524 absDiffErr = np.sqrt(2*(fluxErr*calib)**2 

525 + ((fluxPos + fluxNeg)*calibErr)**2) 

526 

527 self.dataDict["ip_diffim_DipoleFluxNeg_instFlux"] = np.full(self.nRecords, fluxNeg) 

528 self.dataDict["ip_diffim_DipoleFluxNeg_instFluxErr"] = np.full(self.nRecords, fluxErr) 

529 self.dataDict["ip_diffim_DipoleFluxPos_instFlux"] = np.full(self.nRecords, fluxPos) 

530 self.dataDict["ip_diffim_DipoleFluxPos_instFluxErr"] = np.full(self.nRecords, fluxErr) 

531 self.dataDict["base_LocalPhotoCalib"] = np.full(self.nRecords, calib) 

532 self.dataDict["base_LocalPhotoCalibErr"] = np.full(self.nRecords, 

533 calibErr) 

534 

535 df = self.getMultiIndexDataFrame(self.dataDict) 

536 func = LocalDipoleMeanFlux("ip_diffim_DipoleFluxPos_instFlux", 

537 "ip_diffim_DipoleFluxNeg_instFlux", 

538 "ip_diffim_DipoleFluxPos_instFluxErr", 

539 "ip_diffim_DipoleFluxNeg_instFluxErr", 

540 "base_LocalPhotoCalib", 

541 "base_LocalPhotoCalibErr") 

542 val = self._funcVal(func, df) 

543 self.assertTrue(np.allclose(val.values, 

544 absMean, 

545 atol=1e-13, 

546 rtol=0)) 

547 

548 func = LocalDipoleMeanFluxErr("ip_diffim_DipoleFluxPos_instFlux", 

549 "ip_diffim_DipoleFluxNeg_instFlux", 

550 "ip_diffim_DipoleFluxPos_instFluxErr", 

551 "ip_diffim_DipoleFluxNeg_instFluxErr", 

552 "base_LocalPhotoCalib", 

553 "base_LocalPhotoCalibErr") 

554 val = self._funcVal(func, df) 

555 self.assertTrue(np.allclose(val.values, 

556 absMeanErr, 

557 atol=1e-13, 

558 rtol=0)) 

559 

560 func = LocalDipoleDiffFlux("ip_diffim_DipoleFluxPos_instFlux", 

561 "ip_diffim_DipoleFluxNeg_instFlux", 

562 "ip_diffim_DipoleFluxPos_instFluxErr", 

563 "ip_diffim_DipoleFluxNeg_instFluxErr", 

564 "base_LocalPhotoCalib", 

565 "base_LocalPhotoCalibErr") 

566 val = self._funcVal(func, df) 

567 self.assertTrue(np.allclose(val.values, 

568 absDiff, 

569 atol=1e-13, 

570 rtol=0)) 

571 

572 func = LocalDipoleDiffFluxErr("ip_diffim_DipoleFluxPos_instFlux", 

573 "ip_diffim_DipoleFluxNeg_instFlux", 

574 "ip_diffim_DipoleFluxPos_instFluxErr", 

575 "ip_diffim_DipoleFluxNeg_instFluxErr", 

576 "base_LocalPhotoCalib", 

577 "base_LocalPhotoCalibErr") 

578 val = self._funcVal(func, df) 

579 self.assertTrue(np.allclose(val.values, 

580 absDiffErr, 

581 atol=1e-13, 

582 rtol=0)) 

583 

584 def testConvertPixelToArcseconds(self): 

585 """Test calculations of the pixel scale and conversions of pixel to 

586 arcseconds. 

587 """ 

588 dipoleSep = 10 

589 ixx = 10 

590 testPixelDeltas = np.random.uniform(-100, 100, size=(self.nRecords, 2)) 

591 import lsst.afw.table as afwTable 

592 localWcsPlugin = measBase.EvaluateLocalWcsPlugin( 

593 None, 

594 "base_LocalWcs", 

595 afwTable.SourceTable.makeMinimalSchema(), 

596 None) 

597 for dec in np.linspace(-90, 90, 10): 

598 for x, y in zip(np.random.uniform(2 * 1109.99981456774, size=10), 

599 np.random.uniform(2 * 560.018167811613, size=10)): 

600 center = geom.Point2D(x, y) 

601 wcs = self._makeWcs(dec) 

602 skyOrigin = wcs.pixelToSky(center) 

603 

604 linAffMatrix = localWcsPlugin.makeLocalTransformMatrix(wcs, 

605 center) 

606 self.dataDict["dipoleSep"] = np.full(self.nRecords, dipoleSep) 

607 self.dataDict["ixx"] = np.full(self.nRecords, ixx) 

608 self.dataDict["slot_Centroid_x"] = np.full(self.nRecords, x) 

609 self.dataDict["slot_Centroid_y"] = np.full(self.nRecords, y) 

610 self.dataDict["someCentroid_x"] = x + testPixelDeltas[:, 0] 

611 self.dataDict["someCentroid_y"] = y + testPixelDeltas[:, 1] 

612 self.dataDict["base_LocalWcs_CDMatrix_1_1"] = np.full(self.nRecords, 

613 linAffMatrix[0, 0]) 

614 self.dataDict["base_LocalWcs_CDMatrix_1_2"] = np.full(self.nRecords, 

615 linAffMatrix[0, 1]) 

616 self.dataDict["base_LocalWcs_CDMatrix_2_1"] = np.full(self.nRecords, 

617 linAffMatrix[1, 0]) 

618 self.dataDict["base_LocalWcs_CDMatrix_2_2"] = np.full(self.nRecords, 

619 linAffMatrix[1, 1]) 

620 df = self.getMultiIndexDataFrame(self.dataDict) 

621 func = LocalWcs("base_LocalWcs_CDMatrix_1_1", 

622 "base_LocalWcs_CDMatrix_1_2", 

623 "base_LocalWcs_CDMatrix_2_1", 

624 "base_LocalWcs_CDMatrix_2_2") 

625 

626 # Exercise the full set of functions in LocalWcs. 

627 sepRadians = func.getSkySeparationFromPixel( 

628 df[("meas", "g", "someCentroid_x")] - df[("meas", "g", "slot_Centroid_x")], 

629 df[("meas", "g", "someCentroid_y")] - df[("meas", "g", "slot_Centroid_y")], 

630 0.0, 

631 0.0, 

632 df[("meas", "g", "base_LocalWcs_CDMatrix_1_1")], 

633 df[("meas", "g", "base_LocalWcs_CDMatrix_1_2")], 

634 df[("meas", "g", "base_LocalWcs_CDMatrix_2_1")], 

635 df[("meas", "g", "base_LocalWcs_CDMatrix_2_2")]) 

636 

637 # Test functor values against afw SkyWcs computations. 

638 for centX, centY, sep in zip(testPixelDeltas[:, 0], 

639 testPixelDeltas[:, 1], 

640 sepRadians.values): 

641 afwSepRadians = skyOrigin.separation( 

642 wcs.pixelToSky(x + centX, y + centY)).asRadians() 

643 self.assertAlmostEqual(1 - sep / afwSepRadians, 0, places=6) 

644 

645 # Test the pixel scale computation. 

646 func = ComputePixelScale("base_LocalWcs_CDMatrix_1_1", 

647 "base_LocalWcs_CDMatrix_1_2", 

648 "base_LocalWcs_CDMatrix_2_1", 

649 "base_LocalWcs_CDMatrix_2_2") 

650 pixelScale = self._funcVal(func, df) 

651 self.assertTrue(np.allclose( 

652 wcs.getPixelScale(center).asArcseconds(), 

653 pixelScale.values, 

654 rtol=1e-8, 

655 atol=0)) 

656 

657 # Test pixel -> arcsec conversion. 

658 func = ConvertPixelToArcseconds("dipoleSep", 

659 "base_LocalWcs_CDMatrix_1_1", 

660 "base_LocalWcs_CDMatrix_1_2", 

661 "base_LocalWcs_CDMatrix_2_1", 

662 "base_LocalWcs_CDMatrix_2_2") 

663 val = self._funcVal(func, df) 

664 self.assertTrue(np.allclose(pixelScale.values * dipoleSep, 

665 val.values, 

666 atol=1e-16, 

667 rtol=1e-16)) 

668 

669 # Test pixel^2 -> arcsec^2 conversion. 

670 func = ConvertPixelSqToArcsecondsSq("ixx", 

671 "base_LocalWcs_CDMatrix_1_1", 

672 "base_LocalWcs_CDMatrix_1_2", 

673 "base_LocalWcs_CDMatrix_2_1", 

674 "base_LocalWcs_CDMatrix_2_2") 

675 val = self._funcVal(func, df) 

676 self.assertTrue(np.allclose(pixelScale.values ** 2 * dipoleSep, 

677 val.values, 

678 atol=1e-16, 

679 rtol=1e-16)) 

680 

681 def _makeWcs(self, dec=53.1595451514076): 

682 """Create a wcs from real CFHT values. 

683 

684 Returns 

685 ------- 

686 wcs : `lsst.afw.geom` 

687 Created wcs. 

688 """ 

689 metadata = dafBase.PropertySet() 

690 

691 metadata.set("SIMPLE", "T") 

692 metadata.set("BITPIX", -32) 

693 metadata.set("NAXIS", 2) 

694 metadata.set("NAXIS1", 1024) 

695 metadata.set("NAXIS2", 1153) 

696 metadata.set("RADECSYS", 'FK5') 

697 metadata.set("EQUINOX", 2000.) 

698 

699 metadata.setDouble("CRVAL1", 215.604025685476) 

700 metadata.setDouble("CRVAL2", dec) 

701 metadata.setDouble("CRPIX1", 1109.99981456774) 

702 metadata.setDouble("CRPIX2", 560.018167811613) 

703 metadata.set("CTYPE1", 'RA---SIN') 

704 metadata.set("CTYPE2", 'DEC--SIN') 

705 

706 metadata.setDouble("CD1_1", 5.10808596133527E-05) 

707 metadata.setDouble("CD1_2", 1.85579539217196E-07) 

708 metadata.setDouble("CD2_2", -5.10281493481982E-05) 

709 metadata.setDouble("CD2_1", -8.27440751733828E-07) 

710 

711 return afwGeom.makeSkyWcs(metadata) 

712 

713 def testHtm(self): 

714 """Test that HtmIndxes are created as expected. 

715 """ 

716 df = self.getMultiIndexDataFrame(self.dataDict) 

717 func = HtmIndex20("coord_ra", "coord_dec") 

718 

719 val = self._funcVal(func, df) 

720 # Test that the HtmIds come out as the ra/dec in dataDict. 

721 self.assertTrue(np.all(np.equal( 

722 val.values, 

723 [14924528684992, 14924528689697, 14924528501716, 14924526434259, 

724 14924526433879]))) 

725 

726 def testEbv(self): 

727 """Test that EBV works. 

728 """ 

729 df = self.getMultiIndexDataFrame(self.dataDict) 

730 func = Ebv() 

731 

732 val = self._funcVal(func, df) 

733 np.testing.assert_array_almost_equal( 

734 val.values, 

735 [0.029100, 0.029013, 0.028857, 0.028802, 0.028797] 

736 ) 

737 

738 def _dropLevels(self, df): 

739 levelsToDrop = [n for lev, n in zip(df.columns.levels, df.columns.names) if len(lev) == 1] 

740 

741 # Prevent error when trying to drop *all* columns 

742 if len(levelsToDrop) == len(df.columns.names): 

743 levelsToDrop.remove(df.columns.names[-1]) 

744 

745 df.columns = df.columns.droplevel(levelsToDrop) 

746 

747 

748class MyMemoryTestCase(lsst.utils.tests.MemoryTestCase): 

749 pass 

750 

751 

752def setup_module(module): 

753 lsst.utils.tests.init() 

754 

755 

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

757 lsst.utils.tests.init() 

758 unittest.main()