Coverage for tests/test_functors.py: 13%

396 statements  

« prev     ^ index     » next       coverage.py v7.2.6, created at 2023-05-26 02:56 -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, StarGalaxyLabeller, 

40 DeconvolvedMoments, SdssTraceSize, PsfSdssTraceSizeDiff, 

41 HsmTraceSize, PsfHsmTraceSizeDiff, HsmFwhm, 

42 LocalPhotometry, LocalNanojansky, LocalNanojanskyErr, 

43 LocalMagnitude, LocalMagnitudeErr, 

44 LocalDipoleMeanFlux, LocalDipoleMeanFluxErr, 

45 LocalDipoleDiffFlux, LocalDipoleDiffFluxErr, 

46 LocalWcs, ComputePixelScale, ConvertPixelToArcseconds, 

47 ConvertPixelSqToArcsecondsSq, Ratio, HtmIndex20, Ebv) 

48 

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

50 

51 

52class FunctorTestCase(unittest.TestCase): 

53 

54 def getMultiIndexDataFrame(self, dataDict): 

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

56 

57 simpleDF = pd.DataFrame(dataDict) 

58 dfFilterDSCombos = [] 

59 for ds in self.datasets: 

60 for band in self.bands: 

61 df = copy.copy(simpleDF) 

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

63 df['dataset'] = ds 

64 df['band'] = band 

65 df.columns = pd.MultiIndex.from_tuples( 

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

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

68 dfFilterDSCombos.append(df) 

69 

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

71 

72 return df 

73 

74 def getSimpleDataFrame(self, dataDict): 

75 return pd.DataFrame(dataDict) 

76 

77 def getDatasetHandle(self, df): 

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

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

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

81 

82 def setUp(self): 

83 np.random.seed(12345) 

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

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

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

87 self.nRecords = 5 

88 self.dataDict = { 

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

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

91 

92 def _funcVal(self, functor, df): 

93 self.assertIsInstance(functor.name, str) 

94 self.assertIsInstance(functor.shortname, str) 

95 

96 handle = self.getDatasetHandle(df) 

97 

98 val = functor(df) 

99 val2 = functor(handle) 

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

101 self.assertIsInstance(val, pd.Series) 

102 

103 val = functor(df, dropna=True) 

104 val2 = functor(handle, dropna=True) 

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

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

107 

108 return val 

109 

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

111 self.assertIsInstance(functor.name, str) 

112 self.assertIsInstance(functor.shortname, str) 

113 

114 handle1 = self.getDatasetHandle(df1) 

115 handle2 = self.getDatasetHandle(df2) 

116 

117 val = functor.difference(df1, df2) 

118 val2 = functor.difference(handle1, handle2) 

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

120 self.assertIsInstance(val, pd.Series) 

121 

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

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

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

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

126 

127 val1 = self._funcVal(functor, df1) 

128 val2 = self._funcVal(functor, df2) 

129 

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

131 

132 return val 

133 

134 def testColumn(self): 

135 self.columns.append("base_FootprintArea_value") 

136 self.dataDict["base_FootprintArea_value"] = \ 

137 np.full(self.nRecords, 1) 

138 df = self.getMultiIndexDataFrame(self.dataDict) 

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

140 self._funcVal(func, df) 

141 

142 df = self.getSimpleDataFrame(self.dataDict) 

143 func = Column('base_FootprintArea_value') 

144 self._funcVal(func, df) 

145 

146 def testCustom(self): 

147 self.columns.append("base_FootprintArea_value") 

148 self.dataDict["base_FootprintArea_value"] = \ 

149 np.random.rand(self.nRecords) 

150 df = self.getMultiIndexDataFrame(self.dataDict) 

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

152 val = self._funcVal(func, df) 

153 

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

155 

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

157 

158 df = self.getSimpleDataFrame(self.dataDict) 

159 func = CustomFunctor('2 * base_FootprintArea_value') 

160 val = self._funcVal(func, df) 

161 func2 = Column('base_FootprintArea_value') 

162 

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

164 

165 def testCoords(self): 

166 df = self.getMultiIndexDataFrame(self.dataDict) 

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

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

169 

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

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

172 

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

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

175 self._dropLevels(dfSub) 

176 

177 coords = dfSub / np.pi * 180. 

178 

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

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

181 

182 # single-level column index table 

183 df = self.getSimpleDataFrame(self.dataDict) 

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

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

186 

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

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

189 coords = dfSub / np.pi * 180. 

190 

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

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

193 

194 def testMag(self): 

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

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

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

198 df = self.getMultiIndexDataFrame(self.dataDict) 

199 # Change one dataset filter combinations value. 

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

201 

202 fluxName = 'base_PsfFlux' 

203 

204 # Check that things work when you provide dataset explicitly 

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

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

207 filt='g'), 

208 df) 

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

210 filt='r'), 

211 df) 

212 

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

214 dataset=dataset), 

215 df) 

216 

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

218 

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

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

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

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

223 

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

225 

226 # These should *not* be equal. 

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

228 

229 def testMagDiff(self): 

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

231 "modelfit_CModel_instFlux", "modelfit_CModel_instFluxErr"]) 

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

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

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

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

236 df = self.getMultiIndexDataFrame(self.dataDict) 

237 

238 for filt in self.bands: 

239 filt = 'g' 

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

241 

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

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

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

245 

246 def testDifference(self): 

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

248 """ 

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

250 "modelfit_CModel_instFlux", "modelfit_CModel_instFluxErr"]) 

251 

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

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

254 df1 = self.getMultiIndexDataFrame(self.dataDict) 

255 

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

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

258 df2 = self.getMultiIndexDataFrame(self.dataDict) 

259 

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

261 

262 # Asserts that differences computed properly 

263 self._differenceVal(magDiff, df1, df2) 

264 

265 def testLabeller(self): 

266 # Covering the code is better than nothing 

267 self.columns.append("base_ClassificationExtendedness_value") 

268 self.dataDict["base_ClassificationExtendedness_value"] = np.full(self.nRecords, 1) 

269 df = self.getMultiIndexDataFrame(self.dataDict) 

270 _ = self._funcVal(StarGalaxyLabeller(), df) 

271 

272 def testPixelScale(self): 

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

274 # expected. 

275 pass 

276 

277 def testOther(self): 

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

279 "base_SdssShape_xx", "base_SdssShape_yy", 

280 "ext_shapeHSM_HsmPsfMoments_xx", "ext_shapeHSM_HsmPsfMoments_yy", 

281 "base_SdssShape_psf_xx", "base_SdssShape_psf_yy"]) 

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

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

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

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

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

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

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

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

290 df = self.getMultiIndexDataFrame(self.dataDict) 

291 # Covering the code is better than nothing 

292 for filt in self.bands: 

293 for Func in [DeconvolvedMoments, 

294 SdssTraceSize, 

295 PsfSdssTraceSizeDiff, 

296 HsmTraceSize, PsfHsmTraceSizeDiff, HsmFwhm]: 

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

298 

299 def _compositeFuncVal(self, functor, df): 

300 self.assertIsInstance(functor, CompositeFunctor) 

301 

302 handle = self.getDatasetHandle(df) 

303 

304 fdf1 = functor(df) 

305 fdf2 = functor(handle) 

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

307 

308 self.assertIsInstance(fdf1, pd.DataFrame) 

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

310 

311 fdf1 = functor(df, dropna=True) 

312 fdf2 = functor(handle, dropna=True) 

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

314 

315 # Check that there are no nulls 

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

317 

318 return fdf1 

319 

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

321 self.assertIsInstance(functor, CompositeFunctor) 

322 

323 handle1 = self.getDatasetHandle(df1) 

324 handle2 = self.getDatasetHandle(df2) 

325 

326 fdf1 = functor.difference(df1, df2) 

327 fdf2 = functor.difference(handle1, handle2) 

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

329 

330 self.assertIsInstance(fdf1, pd.DataFrame) 

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

332 

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

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

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

336 

337 # Check that there are no nulls 

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

339 

340 df1_functored = functor(df1) 

341 df2_functored = functor(df2) 

342 

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

344 

345 return fdf1 

346 

347 def testComposite(self): 

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

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

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

351 

352 df = self.getMultiIndexDataFrame(self.dataDict) 

353 # Modify r band value slightly. 

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

355 

356 filt = 'g' 

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

358 'ra': RAColumn(), 

359 'dec': DecColumn(), 

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

361 'cmodel_magDiff': MagDiff('base_PsfFlux', 

362 'modelfit_CModel', filt=filt)} 

363 func = CompositeFunctor(funcDict) 

364 fdf1 = self._compositeFuncVal(func, df) 

365 

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

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

368 'ra': RAColumn(), 

369 'dec': DecColumn(), 

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

371 'cmodel_magDiff': MagDiff('base_PsfFlux', 

372 'modelfit_CModel')} 

373 

374 func2 = CompositeFunctor(funcDict2, filt=filt) 

375 fdf2 = self._compositeFuncVal(func2, df) 

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

377 

378 func2.filt = 'r' 

379 fdf3 = self._compositeFuncVal(func2, df) 

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

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

382 

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

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

385 RAColumn(), 

386 DecColumn(), 

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

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

389 

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

391 

392 def testCompositeSimple(self): 

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

394 """ 

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

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

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

398 

399 df = self.getSimpleDataFrame(self.dataDict) 

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

401 'dec': DecColumn(), 

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

403 'cmodel_magDiff': MagDiff('base_PsfFlux', 

404 'modelfit_CModel')} 

405 func = CompositeFunctor(funcDict) 

406 _ = self._compositeFuncVal(func, df) 

407 

408 def testCompositeColor(self): 

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

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

411 df = self.getMultiIndexDataFrame(self.dataDict) 

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

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

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

415 # Covering the code is better than nothing 

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

417 

418 def testCompositeDifference(self): 

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

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

421 df1 = self.getMultiIndexDataFrame(self.dataDict) 

422 

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

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

425 df2 = self.getMultiIndexDataFrame(self.dataDict) 

426 

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

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

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

430 # Covering the code is better than nothing 

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

432 

433 def testCompositeFail(self): 

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

435 """ 

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

437 df = self.getMultiIndexDataFrame(self.dataDict) 

438 

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

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

441 

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

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

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

445 

446 def testLocalPhotometry(self): 

447 """Test the local photometry functors. 

448 """ 

449 flux = 1000 

450 fluxErr = 10 

451 calib = 10 

452 calibErr = 1 

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

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

455 fluxErr) 

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

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

458 calibErr) 

459 df = self.getMultiIndexDataFrame(self.dataDict) 

460 func = LocalPhotometry("base_PsfFlux_instFlux", 

461 "base_PsfFlux_instFluxErr", 

462 "base_LocalPhotoCalib", 

463 "base_LocalPhotoCalibErr") 

464 

465 nanoJansky = func.instFluxToNanojansky( 

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

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

468 mag = func.instFluxToMagnitude( 

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

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

471 nanoJanskyErr = func.instFluxErrToNanojanskyErr( 

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

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

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

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

476 magErr = func.instFluxErrToMagnitudeErr( 

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

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

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

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

481 

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

483 flux * calib, 

484 atol=1e-13, 

485 rtol=0)) 

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

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

488 atol=1e-13, 

489 rtol=0)) 

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

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

492 atol=1e-13, 

493 rtol=0)) 

494 self.assertTrue(np.allclose( 

495 magErr.values, 

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

497 atol=1e-13, 

498 rtol=0)) 

499 

500 # Test functors against the values computed above. 

501 self._testLocalPhotometryFunctors(LocalNanojansky, 

502 df, 

503 nanoJansky) 

504 self._testLocalPhotometryFunctors(LocalNanojanskyErr, 

505 df, 

506 nanoJanskyErr) 

507 self._testLocalPhotometryFunctors(LocalMagnitude, 

508 df, 

509 mag) 

510 self._testLocalPhotometryFunctors(LocalMagnitudeErr, 

511 df, 

512 magErr) 

513 

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

515 func = functor("base_PsfFlux_instFlux", 

516 "base_PsfFlux_instFluxErr", 

517 "base_LocalPhotoCalib", 

518 "base_LocalPhotoCalibErr") 

519 val = self._funcVal(func, df) 

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

521 val.values, 

522 atol=1e-13, 

523 rtol=0)) 

524 

525 def testDipPhotometry(self): 

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

527 fluxNeg = -100 

528 fluxPos = 101 

529 fluxErr = 10 

530 calib = 10 

531 calibErr = 1 

532 

533 # compute expected values. 

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

535 absDiff = (fluxNeg + fluxPos)*calib 

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

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

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

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

540 

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

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

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

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

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

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

547 calibErr) 

548 

549 df = self.getMultiIndexDataFrame(self.dataDict) 

550 func = LocalDipoleMeanFlux("ip_diffim_DipoleFluxPos_instFlux", 

551 "ip_diffim_DipoleFluxNeg_instFlux", 

552 "ip_diffim_DipoleFluxPos_instFluxErr", 

553 "ip_diffim_DipoleFluxNeg_instFluxErr", 

554 "base_LocalPhotoCalib", 

555 "base_LocalPhotoCalibErr") 

556 val = self._funcVal(func, df) 

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

558 absMean, 

559 atol=1e-13, 

560 rtol=0)) 

561 

562 func = LocalDipoleMeanFluxErr("ip_diffim_DipoleFluxPos_instFlux", 

563 "ip_diffim_DipoleFluxNeg_instFlux", 

564 "ip_diffim_DipoleFluxPos_instFluxErr", 

565 "ip_diffim_DipoleFluxNeg_instFluxErr", 

566 "base_LocalPhotoCalib", 

567 "base_LocalPhotoCalibErr") 

568 val = self._funcVal(func, df) 

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

570 absMeanErr, 

571 atol=1e-13, 

572 rtol=0)) 

573 

574 func = LocalDipoleDiffFlux("ip_diffim_DipoleFluxPos_instFlux", 

575 "ip_diffim_DipoleFluxNeg_instFlux", 

576 "ip_diffim_DipoleFluxPos_instFluxErr", 

577 "ip_diffim_DipoleFluxNeg_instFluxErr", 

578 "base_LocalPhotoCalib", 

579 "base_LocalPhotoCalibErr") 

580 val = self._funcVal(func, df) 

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

582 absDiff, 

583 atol=1e-13, 

584 rtol=0)) 

585 

586 func = LocalDipoleDiffFluxErr("ip_diffim_DipoleFluxPos_instFlux", 

587 "ip_diffim_DipoleFluxNeg_instFlux", 

588 "ip_diffim_DipoleFluxPos_instFluxErr", 

589 "ip_diffim_DipoleFluxNeg_instFluxErr", 

590 "base_LocalPhotoCalib", 

591 "base_LocalPhotoCalibErr") 

592 val = self._funcVal(func, df) 

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

594 absDiffErr, 

595 atol=1e-13, 

596 rtol=0)) 

597 

598 def testConvertPixelToArcseconds(self): 

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

600 arcseconds. 

601 """ 

602 dipoleSep = 10 

603 ixx = 10 

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

605 import lsst.afw.table as afwTable 

606 localWcsPlugin = measBase.EvaluateLocalWcsPlugin( 

607 None, 

608 "base_LocalWcs", 

609 afwTable.SourceTable.makeMinimalSchema(), 

610 None) 

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

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

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

614 center = geom.Point2D(x, y) 

615 wcs = self._makeWcs(dec) 

616 skyOrigin = wcs.pixelToSky(center) 

617 

618 linAffMatrix = localWcsPlugin.makeLocalTransformMatrix(wcs, 

619 center) 

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

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

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

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

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

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

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

627 linAffMatrix[0, 0]) 

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

629 linAffMatrix[0, 1]) 

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

631 linAffMatrix[1, 0]) 

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

633 linAffMatrix[1, 1]) 

634 df = self.getMultiIndexDataFrame(self.dataDict) 

635 func = LocalWcs("base_LocalWcs_CDMatrix_1_1", 

636 "base_LocalWcs_CDMatrix_1_2", 

637 "base_LocalWcs_CDMatrix_2_1", 

638 "base_LocalWcs_CDMatrix_2_2") 

639 

640 # Exercise the full set of functions in LocalWcs. 

641 sepRadians = func.getSkySeparationFromPixel( 

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

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

644 0.0, 

645 0.0, 

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

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

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

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

650 

651 # Test functor values against afw SkyWcs computations. 

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

653 testPixelDeltas[:, 1], 

654 sepRadians.values): 

655 afwSepRadians = skyOrigin.separation( 

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

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

658 

659 # Test the pixel scale computation. 

660 func = ComputePixelScale("base_LocalWcs_CDMatrix_1_1", 

661 "base_LocalWcs_CDMatrix_1_2", 

662 "base_LocalWcs_CDMatrix_2_1", 

663 "base_LocalWcs_CDMatrix_2_2") 

664 pixelScale = self._funcVal(func, df) 

665 self.assertTrue(np.allclose( 

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

667 pixelScale.values, 

668 rtol=1e-8, 

669 atol=0)) 

670 

671 # Test pixel -> arcsec conversion. 

672 func = ConvertPixelToArcseconds("dipoleSep", 

673 "base_LocalWcs_CDMatrix_1_1", 

674 "base_LocalWcs_CDMatrix_1_2", 

675 "base_LocalWcs_CDMatrix_2_1", 

676 "base_LocalWcs_CDMatrix_2_2") 

677 val = self._funcVal(func, df) 

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

679 val.values, 

680 atol=1e-16, 

681 rtol=1e-16)) 

682 

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

684 func = ConvertPixelSqToArcsecondsSq("ixx", 

685 "base_LocalWcs_CDMatrix_1_1", 

686 "base_LocalWcs_CDMatrix_1_2", 

687 "base_LocalWcs_CDMatrix_2_1", 

688 "base_LocalWcs_CDMatrix_2_2") 

689 val = self._funcVal(func, df) 

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

691 val.values, 

692 atol=1e-16, 

693 rtol=1e-16)) 

694 

695 def _makeWcs(self, dec=53.1595451514076): 

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

697 

698 Returns 

699 ------- 

700 wcs : `lsst.afw.geom` 

701 Created wcs. 

702 """ 

703 metadata = dafBase.PropertySet() 

704 

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

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

707 metadata.set("NAXIS", 2) 

708 metadata.set("NAXIS1", 1024) 

709 metadata.set("NAXIS2", 1153) 

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

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

712 

713 metadata.setDouble("CRVAL1", 215.604025685476) 

714 metadata.setDouble("CRVAL2", dec) 

715 metadata.setDouble("CRPIX1", 1109.99981456774) 

716 metadata.setDouble("CRPIX2", 560.018167811613) 

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

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

719 

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

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

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

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

724 

725 return afwGeom.makeSkyWcs(metadata) 

726 

727 def testRatio(self): 

728 """Test the ratio functor where one of the functors should be junk. 

729 """ 

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

731 self.dataDict["base_PsfFlux_instFluxErr"] = np.full(self.nRecords, 100) 

732 df = self.getMultiIndexDataFrame(self.dataDict) 

733 

734 func = Ratio("base_PsfFlux_instFlux", "base_PsfFlux_instFluxErr") 

735 

736 val = self._funcVal(func, df) 

737 self.assertTrue(np.allclose(np.full(self.nRecords, 10), 

738 val.values, 

739 atol=1e-16, 

740 rtol=1e-16)) 

741 

742 def testHtm(self): 

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

744 """ 

745 df = self.getMultiIndexDataFrame(self.dataDict) 

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

747 

748 val = self._funcVal(func, df) 

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

750 self.assertTrue(np.all(np.equal( 

751 val.values, 

752 [14924528684992, 14924528689697, 14924528501716, 14924526434259, 

753 14924526433879]))) 

754 

755 def testEbv(self): 

756 """Test that EBV works. 

757 """ 

758 df = self.getMultiIndexDataFrame(self.dataDict) 

759 func = Ebv() 

760 

761 val = self._funcVal(func, df) 

762 np.testing.assert_array_almost_equal( 

763 val.values, 

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

765 ) 

766 

767 def _dropLevels(self, df): 

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

769 

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

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

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

773 

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

775 

776 

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

778 pass 

779 

780 

781def setup_module(module): 

782 lsst.utils.tests.init() 

783 

784 

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

786 lsst.utils.tests.init() 

787 unittest.main()