Coverage for tests / test_functors.py: 10%

522 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-01-09 01:51 -0800

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.afw.table as afwTable 

34import lsst.geom as geom 

35from lsst.sphgeom import HtmPixelization 

36import lsst.meas.base as measBase 

37import lsst.utils.tests 

38from lsst.pipe.base import InMemoryDatasetHandle 

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

40 DecColumn, Mag, MagDiff, Color, 

41 DeconvolvedMoments, SdssTraceSize, PsfSdssTraceSizeDiff, 

42 E1, E2, RadiusFromQuadrupole, 

43 HsmTraceSize, PsfHsmTraceSizeDiff, HsmFwhm, 

44 LocalPhotometry, LocalNanojansky, LocalNanojanskyErr, 

45 LocalDipoleMeanFlux, LocalDipoleMeanFluxErr, 

46 LocalDipoleDiffFlux, LocalDipoleDiffFluxErr, 

47 LocalWcs, ComputePixelScale, ConvertPixelToArcseconds, 

48 ConvertPixelSqToArcsecondsSq, 

49 ConvertDetectorAngleToPositionAngle, 

50 HtmIndex20, Ebv, MomentsIuuSky, MomentsIvvSky, MomentsIuvSky, 

51 SemimajorAxisFromMoments, SemiminorAxisFromMoments, 

52 PositionAngleFromMoments, 

53 CorrelationIuuSky, CorrelationIvvSky, CorrelationIuvSky, 

54 SemimajorAxisFromCorrelation, SemiminorAxisFromCorrelation, 

55 PositionAngleFromCorrelation 

56 ) 

57 

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

59 

60 

61class FunctorTestCase(lsst.utils.tests.TestCase): 

62 

63 def getMultiIndexDataFrame(self, dataDict): 

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

65 

66 simpleDF = pd.DataFrame(dataDict) 

67 dfFilterDSCombos = [] 

68 for ds in self.datasets: 

69 for band in self.bands: 

70 df = copy.copy(simpleDF) 

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

72 df['dataset'] = ds 

73 df['band'] = band 

74 df.columns = pd.MultiIndex.from_tuples( 

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

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

77 dfFilterDSCombos.append(df) 

78 

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

80 

81 return df 

82 

83 def getSimpleDataFrame(self, dataDict): 

84 return pd.DataFrame(dataDict) 

85 

86 def getDatasetHandle(self, df): 

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

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

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

90 

91 def setUp(self): 

92 np.random.seed(12345) 

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

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

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

96 self.nRecords = 5 

97 self.dataDict = { 

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

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

100 

101 def _funcVal(self, functor, df): 

102 self.assertIsInstance(functor.name, str) 

103 self.assertIsInstance(functor.shortname, str) 

104 

105 handle = self.getDatasetHandle(df) 

106 

107 val = functor(df) 

108 val2 = functor(handle) 

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

110 self.assertIsInstance(val, pd.Series) 

111 

112 val = functor(df, dropna=True) 

113 val2 = functor(handle, dropna=True) 

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

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

116 

117 return val 

118 

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

120 self.assertIsInstance(functor.name, str) 

121 self.assertIsInstance(functor.shortname, str) 

122 

123 handle1 = self.getDatasetHandle(df1) 

124 handle2 = self.getDatasetHandle(df2) 

125 

126 val = functor.difference(df1, df2) 

127 val2 = functor.difference(handle1, handle2) 

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

129 self.assertIsInstance(val, pd.Series) 

130 

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

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

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

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

135 

136 val1 = self._funcVal(functor, df1) 

137 val2 = self._funcVal(functor, df2) 

138 

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

140 

141 return val 

142 

143 def testColumn(self): 

144 self.columns.append("base_FootprintArea_value") 

145 self.dataDict["base_FootprintArea_value"] = \ 

146 np.full(self.nRecords, 1) 

147 df = self.getMultiIndexDataFrame(self.dataDict) 

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

149 self._funcVal(func, df) 

150 

151 df = self.getSimpleDataFrame(self.dataDict) 

152 func = Column('base_FootprintArea_value') 

153 self._funcVal(func, df) 

154 

155 def testCustom(self): 

156 self.columns.append("base_FootprintArea_value") 

157 self.dataDict["base_FootprintArea_value"] = \ 

158 np.random.rand(self.nRecords) 

159 df = self.getMultiIndexDataFrame(self.dataDict) 

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

161 val = self._funcVal(func, df) 

162 

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

164 

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

166 

167 df = self.getSimpleDataFrame(self.dataDict) 

168 func = CustomFunctor('2 * base_FootprintArea_value') 

169 val = self._funcVal(func, df) 

170 func2 = Column('base_FootprintArea_value') 

171 

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

173 

174 def testCoords(self): 

175 df = self.getMultiIndexDataFrame(self.dataDict) 

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

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

178 

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

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

181 

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

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

184 self._dropLevels(dfSub) 

185 

186 coords = dfSub / np.pi * 180. 

187 

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

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

190 

191 # single-level column index table 

192 df = self.getSimpleDataFrame(self.dataDict) 

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

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

195 

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

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

198 coords = dfSub / np.pi * 180. 

199 

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

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

202 

203 def testMag(self): 

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

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

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

207 df = self.getMultiIndexDataFrame(self.dataDict) 

208 # Change one dataset filter combinations value. 

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

210 

211 fluxName = 'base_PsfFlux' 

212 

213 # Check that things work when you provide dataset explicitly 

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

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

216 filt='g'), 

217 df) 

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

219 filt='r'), 

220 df) 

221 

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

223 dataset=dataset), 

224 df) 

225 

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

227 

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

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

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

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

232 

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

234 

235 # These should *not* be equal. 

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

237 

238 def testMagDiff(self): 

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

240 "modelfit_CModel_instFlux", "modelfit_CModel_instFluxErr"]) 

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

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

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

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

245 df = self.getMultiIndexDataFrame(self.dataDict) 

246 

247 for filt in self.bands: 

248 filt = 'g' 

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

250 

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

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

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

254 

255 def testDifference(self): 

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

257 """ 

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

259 "modelfit_CModel_instFlux", "modelfit_CModel_instFluxErr"]) 

260 

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

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

263 df1 = self.getMultiIndexDataFrame(self.dataDict) 

264 

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

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

267 df2 = self.getMultiIndexDataFrame(self.dataDict) 

268 

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

270 

271 # Asserts that differences computed properly 

272 self._differenceVal(magDiff, df1, df2) 

273 

274 def testPixelScale(self): 

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

276 # expected. 

277 pass 

278 

279 def testShape(self): 

280 data = { 

281 "x": np.array([-0.3, 0.4, 0.7, -0.9, 1.4, -5.3]), 

282 "y": np.array([1.5, -0.7, -1.9, 2.8, -1.4, 0.01]), 

283 "rho": np.array([-0.9, 0.4, -0.7, 0., 0.3, -0.99]), 

284 } 

285 data["xx"] = data["x"]**2 

286 data["yy"] = data["y"]**2 

287 data["xy"] = data["x"]*data["y"]*data["rho"] 

288 

289 args = ("xx", "xy", "yy") 

290 functor_e1, functor_e2, functor_quadrupole = E1(*args), E2(*args), RadiusFromQuadrupole(*args) 

291 

292 xx_plus_yy = data["xx"] + data["yy"] 

293 data = pd.DataFrame(data) 

294 

295 np.testing.assert_allclose( 

296 functor_e1(data), 

297 ((data["xx"] - data["yy"])/xx_plus_yy).astype(np.float32), 

298 rtol=1e-12, atol=1e-12, 

299 ) 

300 np.testing.assert_allclose( 

301 functor_e2(data), 

302 (2.0*data["xy"]/xx_plus_yy).astype(np.float32), 

303 rtol=1e-12, atol=1e-12, 

304 ) 

305 np.testing.assert_allclose( 

306 functor_quadrupole(data), 

307 ((data["xx"]*data["yy"] - data["xy"]**2)**0.25).astype(np.float32), 

308 rtol=1e-12, atol=1e-12, 

309 ) 

310 

311 def testOther(self): 

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

313 "base_SdssShape_xx", "base_SdssShape_yy", 

314 "ext_shapeHSM_HsmPsfMoments_xx", "ext_shapeHSM_HsmPsfMoments_yy", 

315 "base_SdssShape_psf_xx", "base_SdssShape_psf_yy"]) 

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

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

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

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

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

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

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

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

324 df = self.getMultiIndexDataFrame(self.dataDict) 

325 # Covering the code is better than nothing 

326 for filt in self.bands: 

327 for Func in [DeconvolvedMoments, 

328 SdssTraceSize, 

329 PsfSdssTraceSizeDiff, 

330 HsmTraceSize, PsfHsmTraceSizeDiff, HsmFwhm]: 

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

332 

333 def _compositeFuncVal(self, functor, df): 

334 self.assertIsInstance(functor, CompositeFunctor) 

335 

336 handle = self.getDatasetHandle(df) 

337 

338 fdf1 = functor(df) 

339 fdf2 = functor(handle) 

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

341 

342 self.assertIsInstance(fdf1, pd.DataFrame) 

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

344 

345 fdf1 = functor(df, dropna=True) 

346 fdf2 = functor(handle, dropna=True) 

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

348 

349 # Check that there are no nulls 

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

351 

352 return fdf1 

353 

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

355 self.assertIsInstance(functor, CompositeFunctor) 

356 

357 handle1 = self.getDatasetHandle(df1) 

358 handle2 = self.getDatasetHandle(df2) 

359 

360 fdf1 = functor.difference(df1, df2) 

361 fdf2 = functor.difference(handle1, handle2) 

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

363 

364 self.assertIsInstance(fdf1, pd.DataFrame) 

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

366 

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

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

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

370 

371 # Check that there are no nulls 

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

373 

374 df1_functored = functor(df1) 

375 df2_functored = functor(df2) 

376 

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

378 

379 return fdf1 

380 

381 def testComposite(self): 

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

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

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

385 

386 df = self.getMultiIndexDataFrame(self.dataDict) 

387 # Modify r band value slightly. 

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

389 

390 filt = 'g' 

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

392 'ra': RAColumn(), 

393 'dec': DecColumn(), 

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

395 'cmodel_magDiff': MagDiff('base_PsfFlux', 

396 'modelfit_CModel', filt=filt)} 

397 func = CompositeFunctor(funcDict) 

398 fdf1 = self._compositeFuncVal(func, df) 

399 

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

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

402 'ra': RAColumn(), 

403 'dec': DecColumn(), 

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

405 'cmodel_magDiff': MagDiff('base_PsfFlux', 

406 'modelfit_CModel')} 

407 

408 func2 = CompositeFunctor(funcDict2, filt=filt) 

409 fdf2 = self._compositeFuncVal(func2, df) 

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

411 

412 func2.filt = 'r' 

413 fdf3 = self._compositeFuncVal(func2, df) 

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

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

416 

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

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

419 RAColumn(), 

420 DecColumn(), 

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

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

423 

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

425 

426 def testCompositeSimple(self): 

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

428 """ 

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

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

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

432 

433 df = self.getSimpleDataFrame(self.dataDict) 

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

435 'dec': DecColumn(), 

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

437 'cmodel_magDiff': MagDiff('base_PsfFlux', 

438 'modelfit_CModel')} 

439 func = CompositeFunctor(funcDict) 

440 _ = self._compositeFuncVal(func, df) 

441 

442 def testCompositeColor(self): 

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

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

445 df = self.getMultiIndexDataFrame(self.dataDict) 

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

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

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

449 # Covering the code is better than nothing 

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

451 

452 def testCompositeDifference(self): 

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

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

455 df1 = self.getMultiIndexDataFrame(self.dataDict) 

456 

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

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

459 df2 = self.getMultiIndexDataFrame(self.dataDict) 

460 

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

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

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

464 # Covering the code is better than nothing 

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

466 

467 def testCompositeFail(self): 

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

469 """ 

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

471 df = self.getMultiIndexDataFrame(self.dataDict) 

472 

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

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

475 

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

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

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

479 

480 def testLocalPhotometry(self): 

481 """Test the local photometry functors. 

482 """ 

483 flux = 1000 

484 fluxErr = 10 

485 calib = 10 

486 calibErr = 0.0 

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

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

489 fluxErr) 

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

491 

492 df = self.getMultiIndexDataFrame(self.dataDict) 

493 func = LocalPhotometry("base_PsfFlux_instFlux", 

494 "base_PsfFlux_instFluxErr", 

495 "base_LocalPhotoCalib") 

496 

497 nanoJansky = func.instFluxToNanojansky( 

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

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

500 mag = func.instFluxToMagnitude( 

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

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

503 nanoJanskyErr = func.instFluxErrToNanojanskyErr( 

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

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

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

507 magErr = func.instFluxErrToMagnitudeErr( 

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

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

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

511 

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

513 flux * calib, 

514 atol=0, 

515 rtol=1e-7)) 

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

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

518 atol=0, 

519 rtol=1e-7)) 

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

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

522 atol=0, 

523 rtol=1e-7)) 

524 self.assertTrue(np.allclose( 

525 magErr.values, 

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

527 atol=0, 

528 rtol=1e-7)) 

529 

530 # Test functors against the values computed above. 

531 self._testLocalPhotometryFunctors(LocalNanojansky, 

532 df, 

533 nanoJansky) 

534 self._testLocalPhotometryFunctors(LocalNanojanskyErr, 

535 df, 

536 nanoJanskyErr) 

537 

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

539 func = functor("base_PsfFlux_instFlux", 

540 "base_PsfFlux_instFluxErr", 

541 "base_LocalPhotoCalib") 

542 val = self._funcVal(func, df) 

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

544 val.values, 

545 atol=0, 

546 rtol=1e-7)) 

547 

548 def testDipPhotometry(self): 

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

550 fluxNeg = -100 

551 fluxPos = 101 

552 fluxErr = 10 

553 calib = 10 

554 calibErr = 0.0 

555 

556 # compute expected values. 

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

558 absDiff = (fluxNeg + fluxPos)*calib 

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

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

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

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

563 

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

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

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

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

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

569 

570 df = self.getMultiIndexDataFrame(self.dataDict) 

571 func = LocalDipoleMeanFlux("ip_diffim_DipoleFluxPos_instFlux", 

572 "ip_diffim_DipoleFluxNeg_instFlux", 

573 "ip_diffim_DipoleFluxPos_instFluxErr", 

574 "ip_diffim_DipoleFluxNeg_instFluxErr", 

575 "base_LocalPhotoCalib") 

576 val = self._funcVal(func, df) 

577 self.assertFloatsAlmostEqual(val.values, 

578 absMean, 

579 atol=1e-13, 

580 rtol=0) 

581 

582 func = LocalDipoleMeanFluxErr("ip_diffim_DipoleFluxPos_instFlux", 

583 "ip_diffim_DipoleFluxNeg_instFlux", 

584 "ip_diffim_DipoleFluxPos_instFluxErr", 

585 "ip_diffim_DipoleFluxNeg_instFluxErr", 

586 "base_LocalPhotoCalib") 

587 val = self._funcVal(func, df) 

588 self.assertFloatsAlmostEqual(val.values, 

589 absMeanErr, 

590 atol=1e-13, 

591 rtol=0) 

592 

593 func = LocalDipoleDiffFlux("ip_diffim_DipoleFluxPos_instFlux", 

594 "ip_diffim_DipoleFluxNeg_instFlux", 

595 "ip_diffim_DipoleFluxPos_instFluxErr", 

596 "ip_diffim_DipoleFluxNeg_instFluxErr", 

597 "base_LocalPhotoCalib") 

598 val = self._funcVal(func, df) 

599 self.assertFloatsAlmostEqual(val.values, 

600 absDiff, 

601 atol=1e-13, 

602 rtol=0) 

603 

604 func = LocalDipoleDiffFluxErr("ip_diffim_DipoleFluxPos_instFlux", 

605 "ip_diffim_DipoleFluxNeg_instFlux", 

606 "ip_diffim_DipoleFluxPos_instFluxErr", 

607 "ip_diffim_DipoleFluxNeg_instFluxErr", 

608 "base_LocalPhotoCalib") 

609 val = self._funcVal(func, df) 

610 self.assertFloatsAlmostEqual(val.values, 

611 absDiffErr, 

612 atol=1e-13, 

613 rtol=0) 

614 

615 def testComputePositionAngle(self, offset=0.0001): 

616 """Test computation of position angle from (RA1, Dec1) to (RA2, Dec2) 

617 

618 offset : `float` 

619 Arc length of the offset vector to set up test points. [radian] 

620 """ 

621 

622 d = offset 

623 columns = ("ra1", "dec1", "ra2", "dec2", "expected") 

624 position_angle_test_values = [ 

625 # Get 0, 0 right 

626 (0, 0, d, 0, np.pi/2), 

627 (0, 0, 0, d, 0), 

628 (0, 0, -d, 0, -np.pi/2), 

629 (0, 0, 0, -d, np.pi), 

630 # Make sure we get wrap-around to 0, 0 right 

631 (2*np.pi, 0, d, 0, np.pi/2), 

632 (2*np.pi, 0, 0, d, 0), 

633 (2*np.pi, 0, -d, 0, -np.pi/2), 

634 (2*np.pi, 0, 0, -d, np.pi), 

635 (+0.0015, 0, 2*np.pi - 0.05, 0, -np.pi/2), 

636 # Get another somewhat arbitrary location right [these are in rad] 

637 # It's not really important to rescale d by 1/cos(dec) 

638 # because we're just looking at orientation of vector 

639 # but foreshadowing to the poles... 

640 (0.0015, 1, 0.0015 + d*np.cos(-1), 1, np.pi/2), 

641 (0.0015, 1, 0.0015, 1 + d, 0), 

642 (0.0015, 1, 0.0015 - d*np.cos(-1), 1, - np.pi/2), 

643 (0.0015, 1, 0.0015, 1 - d, np.pi), 

644 # Negative dec 

645 (0.0015, -1, 0.0015 + d*np.cos(-1), -1, np.pi/2), 

646 (0.0015, -1, 0.0015, -1 + d, 0), 

647 (0.0015, -1, 0.0015 - d*np.cos(-1), -1, - np.pi/2), 

648 (0.0015, -1, 0.0015, -1 - d, np.pi), 

649 # Make sure we get wrap-around on that right 

650 (2*np.pi + 0.0015, 1, 2*np.pi + 0.0015 + d*np.cos(1), 1, np.pi/2), 

651 (2*np.pi + 0.0015, 1, 2*np.pi + 0.0015, 1 + d, 0), 

652 (2*np.pi + 0.0015, 1, 0.0015 - d*np.cos(1), 1, - np.pi/2), 

653 (2*np.pi + 0.0015, 1, 0.0015, 1 - d, np.pi), 

654 # Get relative wrap-around right 

655 (2*np.pi + 0.0015, 1, 0.0015 + d*np.cos(1), 1, np.pi/2), 

656 (0.0015, 1, 2*np.pi + 0.0015, 1 + d, 0), 

657 (0.0015, 1, 2*np.pi + 0.0015 - d*np.cos(1), 1, - np.pi/2), 

658 (2*np.pi + 0.0015, 1, 0.0015, 1 - d, np.pi), 

659 # Get either side of RA=0 right 

660 (+ d*np.cos(1) / 2, 1, 2*np.pi - d*np.cos(1) / 2, 1, - np.pi/2), 

661 (+ d*np.cos(1) / 2, 1, - d*np.cos(1) / 2, 1, -np.pi/2), 

662 (2*np.pi + d*np.cos(1) / 2, 1, - d*np.cos(1) / 2, 1, -np.pi/2), 

663 (-d*np.cos(1) / 2, 1, +0.0015, 1, np.pi/2), 

664 (0.0015, 1, 0.0015, 1 + d, 0), 

665 (0.0015, 1, 0.0015 - d*np.cos(1), 1, - np.pi/2), 

666 (0.0015, 1, 0.0015, 1 - d, np.pi), 

667 # Try it near the poles 

668 (0, np.pi/2, 0, np.pi/2 - d, np.pi), 

669 (0, np.pi/2 - d, 0, np.pi/2, 0), 

670 (0, -np.pi/2, 0, -np.pi/2 + d, 0), 

671 (0, -np.pi/2 + d, 0, -np.pi/2, np.pi), 

672 ] 

673 

674 df = pd.DataFrame(position_angle_test_values, columns=columns) 

675 

676 cd_matrix = np.array([[1, 0], [0, -1]]) # Doesn't matter because we don't use it. 

677 local_wcs = LocalWcs(*cd_matrix.flatten()) 

678 pa = local_wcs.computePositionAngle(df["ra1"], df["dec1"], df["ra2"], df["dec2"]) 

679 expected = df["expected"] 

680 tolerance_deg = 0.05 # degrees 

681 tolerance_rad = np.deg2rad(tolerance_deg) 

682 

683 # Use SphereGeom to handle wrap-around separations correctly. 

684 diff = [ 

685 geom.Angle(o, geom.radians).separation(geom.Angle(e, geom.radians)).asRadians() 

686 for o, e in zip(pa, expected) 

687 ] 

688 

689 np.testing.assert_allclose(diff, 0, rtol=0, atol=tolerance_rad) 

690 

691 # Test position angle 

692 def testConvertDetectorAngleToPositionAngle(self): 

693 """Test conversion of position angle in detector degrees to position angle on sky 

694 

695 There is overlap with testConvertPixelToArcseconds 

696 But we also test additional rotation angles so this is separate. 

697 """ 

698 dipoleSep = 10 

699 ixx = 10 

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

701 # testConvertPixelToArcSecond uses a meas_base LocalWcsPlugin 

702 # but we're using a simple WCS as our example, so this doesn't really matter 

703 # and we'll just use the WCS directly 

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

705 for theta in (-45, 0, 90): 

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

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

708 wcs = self._makeWcs(dec=dec, theta=theta) 

709 cd_matrix = wcs.getCdMatrix() 

710 

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

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

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

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

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

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

717 self.dataDict["orientation"] = np.arctan2( 

718 self.dataDict["someCentroid_y"] - self.dataDict["slot_Centroid_y"], 

719 self.dataDict["someCentroid_x"] - self.dataDict["slot_Centroid_x"], 

720 ) 

721 

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

723 cd_matrix[0, 0]) 

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

725 cd_matrix[0, 1]) 

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

727 cd_matrix[1, 0]) 

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

729 cd_matrix[1, 1]) 

730 df = self.getMultiIndexDataFrame(self.dataDict) 

731 

732 # Test detector angle to position angle conversion 

733 func = ConvertDetectorAngleToPositionAngle( 

734 "orientation", 

735 "base_LocalWcs_CDMatrix_1_1", 

736 "base_LocalWcs_CDMatrix_1_2", 

737 "base_LocalWcs_CDMatrix_2_1", 

738 "base_LocalWcs_CDMatrix_2_2" 

739 ) 

740 val = self._funcVal(func, df) 

741 

742 delta_ra, delta_dec = func.computeDeltaRaDec( 

743 self.dataDict["someCentroid_x"] - self.dataDict["slot_Centroid_x"], 

744 self.dataDict["someCentroid_y"] - self.dataDict["slot_Centroid_y"], 

745 self.dataDict["base_LocalWcs_CDMatrix_1_1"], 

746 self.dataDict["base_LocalWcs_CDMatrix_1_2"], 

747 self.dataDict["base_LocalWcs_CDMatrix_2_1"], 

748 self.dataDict["base_LocalWcs_CDMatrix_2_2"], 

749 ) 

750 

751 dx = np.cos(0) * np.tan(delta_dec) - np.sin(0) * np.cos(delta_ra) 

752 dy = np.sin(delta_ra) 

753 comparison_pa = np.arctan2(dy, dx) 

754 comparison_pa = np.rad2deg(comparison_pa) 

755 

756 coord_diff = [] 

757 for v, c in zip(val.values, comparison_pa): 

758 observed_angle = geom.Angle(v, geom.degrees) 

759 expected_angle = geom.Angle(c, geom.degrees) 

760 diff = observed_angle.separation(expected_angle).asRadians() 

761 coord_diff.append(diff) 

762 

763 np.testing.assert_allclose(coord_diff, 0, rtol=0, atol=5e-6) 

764 

765 def testConvertPixelToArcseconds(self): 

766 """Test calculations of the pixel scale, conversions of pixel to 

767 arcseconds. 

768 """ 

769 dipoleSep = 10 

770 ixx = 10 

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

772 localWcsPlugin = measBase.EvaluateLocalWcsPlugin( 

773 None, 

774 "base_LocalWcs", 

775 afwTable.SourceTable.makeMinimalSchema(), 

776 None) 

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

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

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

780 center = geom.Point2D(x, y) 

781 wcs = self._makeWcs(dec) 

782 skyOrigin = wcs.pixelToSky(center) 

783 

784 linAffMatrix = localWcsPlugin.makeLocalTransformMatrix(wcs, 

785 center) 

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

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

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

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

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

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

792 

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

794 linAffMatrix[0, 0]) 

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

796 linAffMatrix[0, 1]) 

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

798 linAffMatrix[1, 0]) 

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

800 linAffMatrix[1, 1]) 

801 df = self.getMultiIndexDataFrame(self.dataDict) 

802 func = LocalWcs("base_LocalWcs_CDMatrix_1_1", 

803 "base_LocalWcs_CDMatrix_1_2", 

804 "base_LocalWcs_CDMatrix_2_1", 

805 "base_LocalWcs_CDMatrix_2_2") 

806 

807 # Exercise the full set of functions in LocalWcs. 

808 sepRadians = func.getSkySeparationFromPixel( 

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

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

811 0.0, 

812 0.0, 

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

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

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

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

817 

818 # Test functor values against afw SkyWcs computations. 

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

820 testPixelDeltas[:, 1], 

821 sepRadians.values): 

822 afwSepRadians = skyOrigin.separation( 

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

824 self.assertAlmostEqual(1 - sep / afwSepRadians, 0, places=5) 

825 

826 # Test the pixel scale computation. 

827 func = ComputePixelScale("base_LocalWcs_CDMatrix_1_1", 

828 "base_LocalWcs_CDMatrix_1_2", 

829 "base_LocalWcs_CDMatrix_2_1", 

830 "base_LocalWcs_CDMatrix_2_2") 

831 pixelScale = self._funcVal(func, df) 

832 self.assertTrue(np.allclose( 

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

834 pixelScale.values, 

835 rtol=1e-6, 

836 atol=0)) 

837 

838 # Test pixel -> arcsec conversion. 

839 func = ConvertPixelToArcseconds("dipoleSep", 

840 "base_LocalWcs_CDMatrix_1_1", 

841 "base_LocalWcs_CDMatrix_1_2", 

842 "base_LocalWcs_CDMatrix_2_1", 

843 "base_LocalWcs_CDMatrix_2_2") 

844 val = self._funcVal(func, df) 

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

846 val.values, 

847 atol=1e-16, 

848 rtol=1e-16)) 

849 

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

851 func = ConvertPixelSqToArcsecondsSq("ixx", 

852 "base_LocalWcs_CDMatrix_1_1", 

853 "base_LocalWcs_CDMatrix_1_2", 

854 "base_LocalWcs_CDMatrix_2_1", 

855 "base_LocalWcs_CDMatrix_2_2") 

856 val = self._funcVal(func, df) 

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

858 val.values, 

859 atol=1e-16, 

860 rtol=1e-16)) 

861 

862 def _makeWcs(self, dec=53.1595451514076, theta=0): 

863 """Create a wcs from real CFHT values, rotated by an optional theta. 

864 

865 dec : `float` 

866 Set reference declination of CRVAL2 [degrees] 

867 theta : `float` 

868 Rotate CD matrix by theta [degrees] 

869 

870 Returns 

871 ------- 

872 wcs : `lsst.afw.geom` 

873 Created wcs. 

874 """ 

875 metadata = dafBase.PropertySet() 

876 

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

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

879 metadata.set("NAXIS", 2) 

880 metadata.set("NAXIS1", 1024) 

881 metadata.set("NAXIS2", 1153) 

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

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

884 

885 metadata.setDouble("CRVAL1", 215.604025685476) 

886 metadata.setDouble("CRVAL2", dec) 

887 metadata.setDouble("CRPIX1", 1109.99981456774) 

888 metadata.setDouble("CRPIX2", 560.018167811613) 

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

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

891 

892 cd_matrix = np.array( 

893 [ 

894 [5.10808596133527E-05, 1.85579539217196E-07], 

895 [-8.27440751733828E-07, -5.10281493481982E-05] 

896 ] 

897 ) 

898 # rotate CD matrix 

899 theta_rad = np.deg2rad(theta) 

900 rotation_matrix = np.array( 

901 [ 

902 [np.cos(theta_rad), -np.sin(theta_rad)], 

903 [np.sin(theta_rad), np.cos(theta_rad)], 

904 ] 

905 ) 

906 cd_matrix = rotation_matrix @ cd_matrix 

907 

908 metadata.setDouble("CD1_1", cd_matrix[0, 0]) 

909 metadata.setDouble("CD1_2", cd_matrix[0, 1]) 

910 metadata.setDouble("CD2_1", cd_matrix[1, 0]) 

911 metadata.setDouble("CD2_2", cd_matrix[1, 1]) 

912 

913 return afwGeom.makeSkyWcs(metadata) 

914 

915 def testHtm(self): 

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

917 """ 

918 df = self.getMultiIndexDataFrame(self.dataDict) 

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

920 

921 val = self._funcVal(func, df) 

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

923 self.assertTrue(np.all(np.equal( 

924 val.values, 

925 [14924528684992, 14924528689697, 14924528501716, 14924526434259, 

926 14924526433879]))) 

927 

928 def testEbv(self): 

929 """Test that EBV works. 

930 """ 

931 df = self.getMultiIndexDataFrame(self.dataDict) 

932 func = Ebv() 

933 

934 val = self._funcVal(func, df) 

935 np.testing.assert_array_almost_equal( 

936 val.values, 

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

938 ) 

939 

940 def testSkyMoments(self): 

941 self.columns.extend([ 

942 "slot_Shape_xx", 

943 "slot_Shape_yy", 

944 "slot_Shape_xy", 

945 "base_LocalWcs_CDMatrix_1_1", 

946 "base_LocalWcs_CDMatrix_1_2", 

947 "base_LocalWcs_CDMatrix_2_1", 

948 "base_LocalWcs_CDMatrix_1_1", 

949 ]) 

950 

951 # CD Matrix from a ComCam exposure. 

952 self.dataDict["base_LocalWcs_CDMatrix_1_1"] = \ 

953 np.full(self.nRecords, -9.695088e-07) 

954 self.dataDict["base_LocalWcs_CDMatrix_1_2"] = \ 

955 np.full(self.nRecords, 3.950301849959342e-09) 

956 self.dataDict["base_LocalWcs_CDMatrix_2_1"] = \ 

957 np.full(self.nRecords, 3.8766915166433014e-09) 

958 self.dataDict["base_LocalWcs_CDMatrix_2_2"] = \ 

959 np.full(self.nRecords, 9.695092484727074e-07) 

960 self.dataDict["slot_Shape_xx"] = \ 

961 np.array([6.52675084, 74.17426471, 6.45283335, 36.82870958, 6.45685472]) 

962 self.dataDict["slot_Shape_yy"] = \ 

963 np.array([6.12848637, 80.99510036, 6.05671667, 35.79219613, 5.97778765]) 

964 self.dataDict["slot_Shape_xy"] = \ 

965 np.array([-0.10281872, 0.88788384, -0.1261287, -1.60504171, 0.11974515]) 

966 self.dataDict["slot_Shape_sigma_x"] = np.sqrt(self.dataDict["slot_Shape_xx"]) 

967 self.dataDict["slot_Shape_sigma_y"] = np.sqrt(self.dataDict["slot_Shape_yy"]) 

968 self.dataDict["slot_Shape_rho"] = self.dataDict["slot_Shape_xy"]/( 

969 self.dataDict["slot_Shape_sigma_x"]*self.dataDict["slot_Shape_sigma_y"] 

970 ) 

971 

972 args_cd = [ 

973 "base_LocalWcs_CDMatrix_1_1", "base_LocalWcs_CDMatrix_1_2", 

974 "base_LocalWcs_CDMatrix_2_1", "base_LocalWcs_CDMatrix_2_2", 

975 ] 

976 args = ["slot_Shape_xx", "slot_Shape_yy", "slot_Shape_xy"] + args_cd 

977 args_corr = ["slot_Shape_sigma_x", "slot_Shape_sigma_y", "slot_Shape_rho"] + args_cd 

978 

979 skyXX_functor = MomentsIuuSky(*args, filt="g") 

980 skyYY_functor = MomentsIvvSky(*args, filt="g") 

981 skyXY_functor = MomentsIuvSky(*args, filt="g") 

982 

983 axesA_functor = SemimajorAxisFromMoments(*args, filt="g") 

984 axesB_functor = SemiminorAxisFromMoments(*args, filt="g") 

985 axesTheta_functor = PositionAngleFromMoments(*args, filt="g") 

986 

987 skyXX_corr_functor = CorrelationIuuSky(*args_corr, filt="g") 

988 skyYY_corr_functor = CorrelationIvvSky(*args_corr, filt="g") 

989 skyXY_corr_functor = CorrelationIuvSky(*args_corr, filt="g") 

990 

991 axesA_corr_functor = SemimajorAxisFromCorrelation(*args_corr, filt="g") 

992 axesB_corr_functor = SemiminorAxisFromCorrelation(*args_corr, filt="g") 

993 axesTheta_corr_functor = PositionAngleFromCorrelation(*args_corr, filt="g") 

994 

995 df = self.getMultiIndexDataFrame(self.dataDict) 

996 output_sky_xx = skyXX_functor(df) 

997 output_sky_yy = skyYY_functor(df) 

998 output_sky_xy = skyXY_functor(df) 

999 

1000 output_axes_a = axesA_functor(df) 

1001 output_axes_b = axesB_functor(df) 

1002 output_axes_theta = axesTheta_functor(df) 

1003 

1004 output_sky_xx_corr = skyXX_corr_functor(df) 

1005 output_sky_yy_corr = skyYY_corr_functor(df) 

1006 output_sky_xy_corr = skyXY_corr_functor(df) 

1007 

1008 output_axes_a_corr = axesA_corr_functor(df) 

1009 output_axes_b_corr = axesB_corr_functor(df) 

1010 output_axes_theta_corr = axesTheta_corr_functor(df) 

1011 

1012 transformed_xx = [] 

1013 transformed_yy = [] 

1014 transformed_xy = [] 

1015 axes_a = [] 

1016 axes_b = [] 

1017 axes_theta = [] 

1018 

1019 for n in range(5): 

1020 Ixx = df[('meas', 'g', 'slot_Shape_xx')].iloc[n] 

1021 Iyy = df[('meas', 'g', 'slot_Shape_yy')].iloc[n] 

1022 Ixy = df[('meas', 'g', 'slot_Shape_xy')].iloc[n] 

1023 localWCS_CD_1_1 = df[('meas', 'g', 'base_LocalWcs_CDMatrix_1_1')].iloc[n] 

1024 localWCS_CD_2_1 = df[('meas', 'g', 'base_LocalWcs_CDMatrix_2_1')].iloc[n] 

1025 localWCS_CD_1_2 = df[('meas', 'g', 'base_LocalWcs_CDMatrix_1_2')].iloc[n] 

1026 localWCS_CD_2_2 = df[('meas', 'g', 'base_LocalWcs_CDMatrix_2_2')].iloc[n] 

1027 CD_matrix = np.array([[localWCS_CD_1_1, localWCS_CD_1_2], 

1028 [localWCS_CD_2_1, localWCS_CD_2_2]]) 

1029 

1030 q = afwGeom.ellipses.Quadrupole(Ixx, Iyy, Ixy) 

1031 lt = geom.LinearTransform(CD_matrix) 

1032 transformed_q = q.transform(lt) 

1033 transformed_q.scale((180/np.pi) * (3600)) 

1034 

1035 axes = afwGeom.ellipses.Axes(transformed_q) 

1036 

1037 transformed_xx.append(transformed_q.getIxx()) 

1038 transformed_yy.append(transformed_q.getIyy()) 

1039 transformed_xy.append(transformed_q.getIxy()) 

1040 

1041 axes_a.append(axes.getA()) 

1042 axes_b.append(axes.getB()) 

1043 axes_theta.append(np.degrees(axes.getTheta())) 

1044 

1045 self.assertFloatsAlmostEqual(output_sky_xx, np.array(transformed_xx), rtol=1e-5) 

1046 self.assertFloatsAlmostEqual(output_sky_yy, np.array(transformed_yy), rtol=1e-5) 

1047 self.assertFloatsAlmostEqual(output_sky_xy, np.array(transformed_xy), rtol=1e-5) 

1048 self.assertFloatsAlmostEqual(output_sky_xx_corr, np.array(transformed_xx), rtol=1e-5) 

1049 self.assertFloatsAlmostEqual(output_sky_yy_corr, np.array(transformed_yy), rtol=1e-5) 

1050 self.assertFloatsAlmostEqual(output_sky_xy_corr, np.array(transformed_xy), rtol=1e-5) 

1051 

1052 self.assertFloatsAlmostEqual(output_axes_a, np.array(axes_a), rtol=1e-5) 

1053 self.assertFloatsAlmostEqual(output_axes_b, np.array(axes_b), rtol=1e-5) 

1054 self.assertFloatsAlmostEqual(output_axes_theta, np.array(axes_theta), rtol=1e-5) 

1055 self.assertFloatsAlmostEqual(output_axes_a_corr, np.array(axes_a), rtol=1e-5) 

1056 self.assertFloatsAlmostEqual(output_axes_b_corr, np.array(axes_b), rtol=1e-5) 

1057 self.assertFloatsAlmostEqual(output_axes_theta_corr, np.array(axes_theta), rtol=1e-5) 

1058 

1059 def _dropLevels(self, df): 

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

1061 

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

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

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

1065 

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

1067 

1068 

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

1070 pass 

1071 

1072 

1073def setup_module(module): 

1074 lsst.utils.tests.init() 

1075 

1076 

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

1078 lsst.utils.tests.init() 

1079 unittest.main()