Coverage for tests/test_functors.py: 12%

388 statements  

« prev     ^ index     » next       coverage.py v7.2.3, created at 2023-04-20 04:18 -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 

29 

30import lsst.daf.base as dafBase 

31import lsst.afw.geom as afwGeom 

32import lsst.geom as geom 

33from lsst.sphgeom import HtmPixelization 

34import lsst.meas.base as measBase 

35import lsst.utils.tests 

36from lsst.pipe.base import InMemoryDatasetHandle 

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

38 DecColumn, Mag, MagDiff, Color, StarGalaxyLabeller, 

39 DeconvolvedMoments, SdssTraceSize, PsfSdssTraceSizeDiff, 

40 HsmTraceSize, PsfHsmTraceSizeDiff, HsmFwhm, 

41 LocalPhotometry, LocalNanojansky, LocalNanojanskyErr, 

42 LocalMagnitude, LocalMagnitudeErr, 

43 LocalDipoleMeanFlux, LocalDipoleMeanFluxErr, 

44 LocalDipoleDiffFlux, LocalDipoleDiffFluxErr, 

45 LocalWcs, ComputePixelScale, ConvertPixelToArcseconds, 

46 ConvertPixelSqToArcsecondsSq, Ratio, HtmIndex20) 

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 testLabeller(self): 

265 # Covering the code is better than nothing 

266 self.columns.append("base_ClassificationExtendedness_value") 

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

268 df = self.getMultiIndexDataFrame(self.dataDict) 

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

270 

271 def testPixelScale(self): 

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

273 # expected. 

274 pass 

275 

276 def testOther(self): 

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

278 "base_SdssShape_xx", "base_SdssShape_yy", 

279 "ext_shapeHSM_HsmPsfMoments_xx", "ext_shapeHSM_HsmPsfMoments_yy", 

280 "base_SdssShape_psf_xx", "base_SdssShape_psf_yy"]) 

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

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

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

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

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

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

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

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

289 df = self.getMultiIndexDataFrame(self.dataDict) 

290 # Covering the code is better than nothing 

291 for filt in self.bands: 

292 for Func in [DeconvolvedMoments, 

293 SdssTraceSize, 

294 PsfSdssTraceSizeDiff, 

295 HsmTraceSize, PsfHsmTraceSizeDiff, HsmFwhm]: 

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

297 

298 def _compositeFuncVal(self, functor, df): 

299 self.assertIsInstance(functor, CompositeFunctor) 

300 

301 handle = self.getDatasetHandle(df) 

302 

303 fdf1 = functor(df) 

304 fdf2 = functor(handle) 

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

306 

307 self.assertIsInstance(fdf1, pd.DataFrame) 

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

309 

310 fdf1 = functor(df, dropna=True) 

311 fdf2 = functor(handle, dropna=True) 

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

313 

314 # Check that there are no nulls 

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

316 

317 return fdf1 

318 

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

320 self.assertIsInstance(functor, CompositeFunctor) 

321 

322 handle1 = self.getDatasetHandle(df1) 

323 handle2 = self.getDatasetHandle(df2) 

324 

325 fdf1 = functor.difference(df1, df2) 

326 fdf2 = functor.difference(handle1, handle2) 

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

328 

329 self.assertIsInstance(fdf1, pd.DataFrame) 

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

331 

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

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

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

335 

336 # Check that there are no nulls 

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

338 

339 df1_functored = functor(df1) 

340 df2_functored = functor(df2) 

341 

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

343 

344 return fdf1 

345 

346 def testComposite(self): 

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

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

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

350 

351 df = self.getMultiIndexDataFrame(self.dataDict) 

352 # Modify r band value slightly. 

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

354 

355 filt = 'g' 

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

357 'ra': RAColumn(), 

358 'dec': DecColumn(), 

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

360 'cmodel_magDiff': MagDiff('base_PsfFlux', 

361 'modelfit_CModel', filt=filt)} 

362 func = CompositeFunctor(funcDict) 

363 fdf1 = self._compositeFuncVal(func, df) 

364 

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

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

367 'ra': RAColumn(), 

368 'dec': DecColumn(), 

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

370 'cmodel_magDiff': MagDiff('base_PsfFlux', 

371 'modelfit_CModel')} 

372 

373 func2 = CompositeFunctor(funcDict2, filt=filt) 

374 fdf2 = self._compositeFuncVal(func2, df) 

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

376 

377 func2.filt = 'r' 

378 fdf3 = self._compositeFuncVal(func2, df) 

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

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

381 

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

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

384 RAColumn(), 

385 DecColumn(), 

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

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

388 

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

390 

391 def testCompositeSimple(self): 

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

393 """ 

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

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

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

397 

398 df = self.getSimpleDataFrame(self.dataDict) 

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

400 'dec': DecColumn(), 

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

402 'cmodel_magDiff': MagDiff('base_PsfFlux', 

403 'modelfit_CModel')} 

404 func = CompositeFunctor(funcDict) 

405 _ = self._compositeFuncVal(func, df) 

406 

407 def testCompositeColor(self): 

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

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

410 df = self.getMultiIndexDataFrame(self.dataDict) 

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

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

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

414 # Covering the code is better than nothing 

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

416 

417 def testCompositeDifference(self): 

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

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

420 df1 = self.getMultiIndexDataFrame(self.dataDict) 

421 

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

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

424 df2 = self.getMultiIndexDataFrame(self.dataDict) 

425 

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

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

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

429 # Covering the code is better than nothing 

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

431 

432 def testCompositeFail(self): 

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

434 """ 

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

436 df = self.getMultiIndexDataFrame(self.dataDict) 

437 

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

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

440 

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

442 

443 def testLocalPhotometry(self): 

444 """Test the local photometry functors. 

445 """ 

446 flux = 1000 

447 fluxErr = 10 

448 calib = 10 

449 calibErr = 1 

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

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

452 fluxErr) 

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

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

455 calibErr) 

456 df = self.getMultiIndexDataFrame(self.dataDict) 

457 func = LocalPhotometry("base_PsfFlux_instFlux", 

458 "base_PsfFlux_instFluxErr", 

459 "base_LocalPhotoCalib", 

460 "base_LocalPhotoCalibErr") 

461 

462 nanoJansky = func.instFluxToNanojansky( 

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

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

465 mag = func.instFluxToMagnitude( 

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

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

468 nanoJanskyErr = func.instFluxErrToNanojanskyErr( 

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 magErr = func.instFluxErrToMagnitudeErr( 

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

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

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

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

478 

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

480 flux * calib, 

481 atol=1e-13, 

482 rtol=0)) 

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

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

485 atol=1e-13, 

486 rtol=0)) 

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

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

489 atol=1e-13, 

490 rtol=0)) 

491 self.assertTrue(np.allclose( 

492 magErr.values, 

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

494 atol=1e-13, 

495 rtol=0)) 

496 

497 # Test functors against the values computed above. 

498 self._testLocalPhotometryFunctors(LocalNanojansky, 

499 df, 

500 nanoJansky) 

501 self._testLocalPhotometryFunctors(LocalNanojanskyErr, 

502 df, 

503 nanoJanskyErr) 

504 self._testLocalPhotometryFunctors(LocalMagnitude, 

505 df, 

506 mag) 

507 self._testLocalPhotometryFunctors(LocalMagnitudeErr, 

508 df, 

509 magErr) 

510 

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

512 func = functor("base_PsfFlux_instFlux", 

513 "base_PsfFlux_instFluxErr", 

514 "base_LocalPhotoCalib", 

515 "base_LocalPhotoCalibErr") 

516 val = self._funcVal(func, df) 

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

518 val.values, 

519 atol=1e-13, 

520 rtol=0)) 

521 

522 def testDipPhotometry(self): 

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

524 fluxNeg = -100 

525 fluxPos = 101 

526 fluxErr = 10 

527 calib = 10 

528 calibErr = 1 

529 

530 # compute expected values. 

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

532 absDiff = (fluxNeg + fluxPos)*calib 

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

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

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

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

537 

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

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

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

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

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

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

544 calibErr) 

545 

546 df = self.getMultiIndexDataFrame(self.dataDict) 

547 func = LocalDipoleMeanFlux("ip_diffim_DipoleFluxPos_instFlux", 

548 "ip_diffim_DipoleFluxNeg_instFlux", 

549 "ip_diffim_DipoleFluxPos_instFluxErr", 

550 "ip_diffim_DipoleFluxNeg_instFluxErr", 

551 "base_LocalPhotoCalib", 

552 "base_LocalPhotoCalibErr") 

553 val = self._funcVal(func, df) 

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

555 absMean, 

556 atol=1e-13, 

557 rtol=0)) 

558 

559 func = LocalDipoleMeanFluxErr("ip_diffim_DipoleFluxPos_instFlux", 

560 "ip_diffim_DipoleFluxNeg_instFlux", 

561 "ip_diffim_DipoleFluxPos_instFluxErr", 

562 "ip_diffim_DipoleFluxNeg_instFluxErr", 

563 "base_LocalPhotoCalib", 

564 "base_LocalPhotoCalibErr") 

565 val = self._funcVal(func, df) 

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

567 absMeanErr, 

568 atol=1e-13, 

569 rtol=0)) 

570 

571 func = LocalDipoleDiffFlux("ip_diffim_DipoleFluxPos_instFlux", 

572 "ip_diffim_DipoleFluxNeg_instFlux", 

573 "ip_diffim_DipoleFluxPos_instFluxErr", 

574 "ip_diffim_DipoleFluxNeg_instFluxErr", 

575 "base_LocalPhotoCalib", 

576 "base_LocalPhotoCalibErr") 

577 val = self._funcVal(func, df) 

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

579 absDiff, 

580 atol=1e-13, 

581 rtol=0)) 

582 

583 func = LocalDipoleDiffFluxErr("ip_diffim_DipoleFluxPos_instFlux", 

584 "ip_diffim_DipoleFluxNeg_instFlux", 

585 "ip_diffim_DipoleFluxPos_instFluxErr", 

586 "ip_diffim_DipoleFluxNeg_instFluxErr", 

587 "base_LocalPhotoCalib", 

588 "base_LocalPhotoCalibErr") 

589 val = self._funcVal(func, df) 

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

591 absDiffErr, 

592 atol=1e-13, 

593 rtol=0)) 

594 

595 def testConvertPixelToArcseconds(self): 

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

597 arcseconds. 

598 """ 

599 dipoleSep = 10 

600 ixx = 10 

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

602 import lsst.afw.table as afwTable 

603 localWcsPlugin = measBase.EvaluateLocalWcsPlugin( 

604 None, 

605 "base_LocalWcs", 

606 afwTable.SourceTable.makeMinimalSchema(), 

607 None) 

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

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

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

611 center = geom.Point2D(x, y) 

612 wcs = self._makeWcs(dec) 

613 skyOrigin = wcs.pixelToSky(center) 

614 

615 linAffMatrix = localWcsPlugin.makeLocalTransformMatrix(wcs, 

616 center) 

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

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

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

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

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

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

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

624 linAffMatrix[0, 0]) 

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

626 linAffMatrix[0, 1]) 

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

628 linAffMatrix[1, 0]) 

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

630 linAffMatrix[1, 1]) 

631 df = self.getMultiIndexDataFrame(self.dataDict) 

632 func = LocalWcs("base_LocalWcs_CDMatrix_1_1", 

633 "base_LocalWcs_CDMatrix_1_2", 

634 "base_LocalWcs_CDMatrix_2_1", 

635 "base_LocalWcs_CDMatrix_2_2") 

636 

637 # Exercise the full set of functions in LocalWcs. 

638 sepRadians = func.getSkySeperationFromPixel( 

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

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

641 0.0, 

642 0.0, 

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

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

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

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

647 

648 # Test functor values against afw SkyWcs computations. 

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

650 testPixelDeltas[:, 1], 

651 sepRadians.values): 

652 afwSepRadians = skyOrigin.separation( 

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

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

655 

656 # Test the pixel scale computation. 

657 func = ComputePixelScale("base_LocalWcs_CDMatrix_1_1", 

658 "base_LocalWcs_CDMatrix_1_2", 

659 "base_LocalWcs_CDMatrix_2_1", 

660 "base_LocalWcs_CDMatrix_2_2") 

661 pixelScale = self._funcVal(func, df) 

662 self.assertTrue(np.allclose( 

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

664 pixelScale.values, 

665 rtol=1e-8, 

666 atol=0)) 

667 

668 # Test pixel -> arcsec conversion. 

669 func = ConvertPixelToArcseconds("dipoleSep", 

670 "base_LocalWcs_CDMatrix_1_1", 

671 "base_LocalWcs_CDMatrix_1_2", 

672 "base_LocalWcs_CDMatrix_2_1", 

673 "base_LocalWcs_CDMatrix_2_2") 

674 val = self._funcVal(func, df) 

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

676 val.values, 

677 atol=1e-16, 

678 rtol=1e-16)) 

679 

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

681 func = ConvertPixelSqToArcsecondsSq("ixx", 

682 "base_LocalWcs_CDMatrix_1_1", 

683 "base_LocalWcs_CDMatrix_1_2", 

684 "base_LocalWcs_CDMatrix_2_1", 

685 "base_LocalWcs_CDMatrix_2_2") 

686 val = self._funcVal(func, df) 

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

688 val.values, 

689 atol=1e-16, 

690 rtol=1e-16)) 

691 

692 def _makeWcs(self, dec=53.1595451514076): 

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

694 

695 Returns 

696 ------- 

697 wcs : `lsst.afw.geom` 

698 Created wcs. 

699 """ 

700 metadata = dafBase.PropertySet() 

701 

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

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

704 metadata.set("NAXIS", 2) 

705 metadata.set("NAXIS1", 1024) 

706 metadata.set("NAXIS2", 1153) 

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

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

709 

710 metadata.setDouble("CRVAL1", 215.604025685476) 

711 metadata.setDouble("CRVAL2", dec) 

712 metadata.setDouble("CRPIX1", 1109.99981456774) 

713 metadata.setDouble("CRPIX2", 560.018167811613) 

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

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

716 

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

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

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

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

721 

722 return afwGeom.makeSkyWcs(metadata) 

723 

724 def testRatio(self): 

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

726 """ 

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

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

729 df = self.getMultiIndexDataFrame(self.dataDict) 

730 

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

732 

733 val = self._funcVal(func, df) 

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

735 val.values, 

736 atol=1e-16, 

737 rtol=1e-16)) 

738 

739 def testHtm(self): 

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

741 """ 

742 df = self.getMultiIndexDataFrame(self.dataDict) 

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

744 

745 val = self._funcVal(func, df) 

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

747 self.assertTrue(np.all(np.equal( 

748 val.values, 

749 [14924528684992, 14924528689697, 14924528501716, 14924526434259, 

750 14924526433879]))) 

751 

752 def _dropLevels(self, df): 

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

754 

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

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

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

758 

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

760 

761 

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

763 pass 

764 

765 

766def setup_module(module): 

767 lsst.utils.tests.init() 

768 

769 

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

771 lsst.utils.tests.init() 

772 unittest.main()