Coverage for tests/test_functors.py: 12%
382 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-08 06:53 -0700
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-08 06:53 -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/>.
22import astropy.units as u
23import copy
24import functools
25import numpy as np
26import os
27import pandas as pd
28import unittest
29import logging
31import lsst.daf.base as dafBase
32import lsst.afw.geom as afwGeom
33import lsst.geom as geom
34from lsst.sphgeom import HtmPixelization
35import lsst.meas.base as measBase
36import lsst.utils.tests
37from lsst.pipe.base import InMemoryDatasetHandle
38from lsst.pipe.tasks.functors import (CompositeFunctor, CustomFunctor, Column, RAColumn,
39 DecColumn, Mag, MagDiff, Color,
40 DeconvolvedMoments, SdssTraceSize, PsfSdssTraceSizeDiff,
41 HsmTraceSize, PsfHsmTraceSizeDiff, HsmFwhm,
42 LocalPhotometry, LocalNanojansky, LocalNanojanskyErr,
43 LocalDipoleMeanFlux, LocalDipoleMeanFluxErr,
44 LocalDipoleDiffFlux, LocalDipoleDiffFluxErr,
45 LocalWcs, ComputePixelScale, ConvertPixelToArcseconds,
46 ConvertPixelSqToArcsecondsSq, HtmIndex20, Ebv)
48ROOT = os.path.abspath(os.path.dirname(__file__))
51class FunctorTestCase(unittest.TestCase):
53 def getMultiIndexDataFrame(self, dataDict):
54 """Create a simple test multi-index DataFrame."""
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)
69 df = functools.reduce(lambda d1, d2: d1.join(d2), dfFilterDSCombos)
71 return df
73 def getSimpleDataFrame(self, dataDict):
74 return pd.DataFrame(dataDict)
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})
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]}
91 def _funcVal(self, functor, df):
92 self.assertIsInstance(functor.name, str)
93 self.assertIsInstance(functor.shortname, str)
95 handle = self.getDatasetHandle(df)
97 val = functor(df)
98 val2 = functor(handle)
99 self.assertTrue((val == val2).all())
100 self.assertIsInstance(val, pd.Series)
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)
107 return val
109 def _differenceVal(self, functor, df1, df2):
110 self.assertIsInstance(functor.name, str)
111 self.assertIsInstance(functor.shortname, str)
113 handle1 = self.getDatasetHandle(df1)
114 handle2 = self.getDatasetHandle(df2)
116 val = functor.difference(df1, df2)
117 val2 = functor.difference(handle1, handle2)
118 self.assertTrue(val.equals(val2))
119 self.assertIsInstance(val, pd.Series)
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)
126 val1 = self._funcVal(functor, df1)
127 val2 = self._funcVal(functor, df2)
129 self.assertTrue(np.allclose(val, val1 - val2))
131 return val
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)
141 df = self.getSimpleDataFrame(self.dataDict)
142 func = Column('base_FootprintArea_value')
143 self._funcVal(func, df)
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)
153 func2 = Column('base_FootprintArea_value', filt='g')
155 np.allclose(val.values, 2*func2(df).values, atol=1e-13, rtol=0)
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')
162 np.allclose(val.values, 2*func2(df).values, atol=1e-13, rtol=0)
164 def testCoords(self):
165 df = self.getMultiIndexDataFrame(self.dataDict)
166 ra = self._funcVal(RAColumn(), df)
167 dec = self._funcVal(DecColumn(), df)
169 columnDict = {'dataset': 'ref', 'band': 'g',
170 'column': ['coord_ra', 'coord_dec']}
172 handle = InMemoryDatasetHandle(df, storageClass="DataFrame")
173 dfSub = handle.get(parameters={"columns": columnDict})
174 self._dropLevels(dfSub)
176 coords = dfSub / np.pi * 180.
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))
181 # single-level column index table
182 df = self.getSimpleDataFrame(self.dataDict)
183 ra = self._funcVal(RAColumn(), df)
184 dec = self._funcVal(DecColumn(), df)
186 handle = InMemoryDatasetHandle(df, storageClass="DataFrame")
187 dfSub = handle.get(parameters={"columns": ['coord_ra', 'coord_dec']})
188 coords = dfSub / np.pi * 180.
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))
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
201 fluxName = 'base_PsfFlux'
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)
212 psfColor_GR = self._funcVal(Color(fluxName, 'g', 'r',
213 dataset=dataset),
214 df)
216 self.assertTrue(np.allclose((psfMag_G - psfMag_R).dropna(), psfColor_GR, rtol=0, atol=1e-13))
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)
223 psfColor_GR = self._funcVal(Color(fluxName, 'g', 'r'), df)
225 # These should *not* be equal.
226 self.assertFalse(np.allclose((psfMag_G - psfMag_R).dropna(), psfColor_GR))
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)
237 for filt in self.bands:
238 filt = 'g'
239 val = self._funcVal(MagDiff('base_PsfFlux', 'modelfit_CModel', filt=filt), df)
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))
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"])
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)
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)
259 magDiff = MagDiff('base_PsfFlux', 'modelfit_CModel', filt='g')
261 # Asserts that differences computed properly
262 self._differenceVal(magDiff, df1, df2)
264 def testPixelScale(self):
265 # Test that the pixel scale and pix->arcsec calculations perform as
266 # expected.
267 pass
269 def testOther(self):
270 self.columns.extend(["ext_shapeHSM_HsmSourceMoments_xx", "ext_shapeHSM_HsmSourceMoments_yy",
271 "base_SdssShape_xx", "base_SdssShape_yy",
272 "ext_shapeHSM_HsmPsfMoments_xx", "ext_shapeHSM_HsmPsfMoments_yy",
273 "base_SdssShape_psf_xx", "base_SdssShape_psf_yy"])
274 self.dataDict["ext_shapeHSM_HsmSourceMoments_xx"] = np.full(self.nRecords, 1 / np.sqrt(2))
275 self.dataDict["ext_shapeHSM_HsmSourceMoments_yy"] = np.full(self.nRecords, 1 / np.sqrt(2))
276 self.dataDict["base_SdssShape_xx"] = np.full(self.nRecords, 1 / np.sqrt(2))
277 self.dataDict["base_SdssShape_yy"] = np.full(self.nRecords, 1 / np.sqrt(2))
278 self.dataDict["ext_shapeHSM_HsmPsfMoments_xx"] = np.full(self.nRecords, 1 / np.sqrt(2))
279 self.dataDict["ext_shapeHSM_HsmPsfMoments_yy"] = np.full(self.nRecords, 1 / np.sqrt(2))
280 self.dataDict["base_SdssShape_psf_xx"] = np.full(self.nRecords, 1)
281 self.dataDict["base_SdssShape_psf_yy"] = np.full(self.nRecords, 1)
282 df = self.getMultiIndexDataFrame(self.dataDict)
283 # Covering the code is better than nothing
284 for filt in self.bands:
285 for Func in [DeconvolvedMoments,
286 SdssTraceSize,
287 PsfSdssTraceSizeDiff,
288 HsmTraceSize, PsfHsmTraceSizeDiff, HsmFwhm]:
289 _ = self._funcVal(Func(filt=filt), df)
291 def _compositeFuncVal(self, functor, df):
292 self.assertIsInstance(functor, CompositeFunctor)
294 handle = self.getDatasetHandle(df)
296 fdf1 = functor(df)
297 fdf2 = functor(handle)
298 self.assertTrue(fdf1.equals(fdf2))
300 self.assertIsInstance(fdf1, pd.DataFrame)
301 self.assertTrue(np.all([k in fdf1.columns for k in functor.funcDict.keys()]))
303 fdf1 = functor(df, dropna=True)
304 fdf2 = functor(handle, dropna=True)
305 self.assertTrue(fdf1.equals(fdf2))
307 # Check that there are no nulls
308 self.assertFalse(fdf1.isnull().any(axis=None))
310 return fdf1
312 def _compositeDifferenceVal(self, functor, df1, df2):
313 self.assertIsInstance(functor, CompositeFunctor)
315 handle1 = self.getDatasetHandle(df1)
316 handle2 = self.getDatasetHandle(df2)
318 fdf1 = functor.difference(df1, df2)
319 fdf2 = functor.difference(handle1, handle2)
320 self.assertTrue(fdf1.equals(fdf2))
322 self.assertIsInstance(fdf1, pd.DataFrame)
323 self.assertTrue(np.all([k in fdf1.columns for k in functor.funcDict.keys()]))
325 fdf1 = functor.difference(df1, df2, dropna=True)
326 fdf2 = functor.difference(handle1, handle2, dropna=True)
327 self.assertTrue(fdf1.equals(fdf2))
329 # Check that there are no nulls
330 self.assertFalse(fdf1.isnull().any(axis=None))
332 df1_functored = functor(df1)
333 df2_functored = functor(df2)
335 self.assertTrue(np.allclose(fdf1.values, df1_functored.values - df2_functored.values))
337 return fdf1
339 def testComposite(self):
340 self.columns.extend(["modelfit_CModel_instFlux", "base_PsfFlux_instFlux"])
341 self.dataDict["modelfit_CModel_instFlux"] = np.full(self.nRecords, 1)
342 self.dataDict["base_PsfFlux_instFlux"] = np.full(self.nRecords, 1)
344 df = self.getMultiIndexDataFrame(self.dataDict)
345 # Modify r band value slightly.
346 df[("meas", "r", "base_PsfFlux_instFlux")] -= 0.1
348 filt = 'g'
349 funcDict = {'psfMag_ref': Mag('base_PsfFlux', dataset='ref'),
350 'ra': RAColumn(),
351 'dec': DecColumn(),
352 'psfMag': Mag('base_PsfFlux', filt=filt),
353 'cmodel_magDiff': MagDiff('base_PsfFlux',
354 'modelfit_CModel', filt=filt)}
355 func = CompositeFunctor(funcDict)
356 fdf1 = self._compositeFuncVal(func, df)
358 # Repeat same, but define filter globally instead of individually
359 funcDict2 = {'psfMag_ref': Mag('base_PsfFlux', dataset='ref'),
360 'ra': RAColumn(),
361 'dec': DecColumn(),
362 'psfMag': Mag('base_PsfFlux'),
363 'cmodel_magDiff': MagDiff('base_PsfFlux',
364 'modelfit_CModel')}
366 func2 = CompositeFunctor(funcDict2, filt=filt)
367 fdf2 = self._compositeFuncVal(func2, df)
368 self.assertTrue(fdf1.equals(fdf2))
370 func2.filt = 'r'
371 fdf3 = self._compositeFuncVal(func2, df)
372 # Because we modified the R filter this should fail.
373 self.assertFalse(fdf2.equals(fdf3))
375 # Make sure things work with passing list instead of dict
376 funcs = [Mag('base_PsfFlux', dataset='ref'),
377 RAColumn(),
378 DecColumn(),
379 Mag('base_PsfFlux', filt=filt),
380 MagDiff('base_PsfFlux', 'modelfit_CModel', filt=filt)]
382 _ = self._compositeFuncVal(CompositeFunctor(funcs), df)
384 def testCompositeSimple(self):
385 """Test single-level composite functor for functionality
386 """
387 self.columns.extend(["modelfit_CModel_instFlux", "base_PsfFlux_instFlux"])
388 self.dataDict["modelfit_CModel_instFlux"] = np.full(self.nRecords, 1)
389 self.dataDict["base_PsfFlux_instFlux"] = np.full(self.nRecords, 1)
391 df = self.getSimpleDataFrame(self.dataDict)
392 funcDict = {'ra': RAColumn(),
393 'dec': DecColumn(),
394 'psfMag': Mag('base_PsfFlux'),
395 'cmodel_magDiff': MagDiff('base_PsfFlux',
396 'modelfit_CModel')}
397 func = CompositeFunctor(funcDict)
398 _ = self._compositeFuncVal(func, df)
400 def testCompositeColor(self):
401 self.dataDict["base_PsfFlux_instFlux"] = np.full(self.nRecords, 1000)
402 self.dataDict["base_PsfFlux_instFluxErr"] = np.full(self.nRecords, 10)
403 df = self.getMultiIndexDataFrame(self.dataDict)
404 funcDict = {'a': Mag('base_PsfFlux', dataset='meas', filt='g'),
405 'b': Mag('base_PsfFlux', dataset='forced_src', filt='g'),
406 'c': Color('base_PsfFlux', 'g', 'r')}
407 # Covering the code is better than nothing
408 _ = self._compositeFuncVal(CompositeFunctor(funcDict), df)
410 def testCompositeDifference(self):
411 self.dataDict["base_PsfFlux_instFlux"] = np.full(self.nRecords, 1000)
412 self.dataDict["base_PsfFlux_instFluxErr"] = np.full(self.nRecords, 10)
413 df1 = self.getMultiIndexDataFrame(self.dataDict)
415 self.dataDict["base_PsfFlux_instFlux"] = np.full(self.nRecords, 999)
416 self.dataDict["base_PsfFlux_instFluxErr"] = np.full(self.nRecords, 9)
417 df2 = self.getMultiIndexDataFrame(self.dataDict)
419 funcDict = {'a': Mag('base_PsfFlux', dataset='meas', filt='g'),
420 'b': Mag('base_PsfFlux', dataset='forced_src', filt='g'),
421 'c': Color('base_PsfFlux', 'g', 'r')}
422 # Covering the code is better than nothing
423 _ = self._compositeDifferenceVal(CompositeFunctor(funcDict), df1, df2)
425 def testCompositeFail(self):
426 """Test a composite functor where one of the functors should be junk.
427 """
428 self.dataDict["base_PsfFlux_instFlux"] = np.full(self.nRecords, 1000)
429 df = self.getMultiIndexDataFrame(self.dataDict)
431 funcDict = {'good': Column("base_PsfFlux_instFlux"),
432 'bad': Column('not_a_column')}
434 with self.assertLogs(level=logging.ERROR) as cm:
435 _ = self._compositeFuncVal(CompositeFunctor(funcDict), df)
436 self.assertIn("Exception in CompositeFunctor (funcs: ['good', 'bad'])", cm.output[0])
438 def testLocalPhotometry(self):
439 """Test the local photometry functors.
440 """
441 flux = 1000
442 fluxErr = 10
443 calib = 10
444 calibErr = 1
445 self.dataDict["base_PsfFlux_instFlux"] = np.full(self.nRecords, flux)
446 self.dataDict["base_PsfFlux_instFluxErr"] = np.full(self.nRecords,
447 fluxErr)
448 self.dataDict["base_LocalPhotoCalib"] = np.full(self.nRecords, calib)
449 self.dataDict["base_LocalPhotoCalibErr"] = np.full(self.nRecords,
450 calibErr)
451 df = self.getMultiIndexDataFrame(self.dataDict)
452 func = LocalPhotometry("base_PsfFlux_instFlux",
453 "base_PsfFlux_instFluxErr",
454 "base_LocalPhotoCalib",
455 "base_LocalPhotoCalibErr")
457 nanoJansky = func.instFluxToNanojansky(
458 df[("meas", "g", "base_PsfFlux_instFlux")],
459 df[("meas", "g", "base_LocalPhotoCalib")])
460 mag = func.instFluxToMagnitude(
461 df[("meas", "g", "base_PsfFlux_instFlux")],
462 df[("meas", "g", "base_LocalPhotoCalib")])
463 nanoJanskyErr = func.instFluxErrToNanojanskyErr(
464 df[("meas", "g", "base_PsfFlux_instFlux")],
465 df[("meas", "g", "base_PsfFlux_instFluxErr")],
466 df[("meas", "g", "base_LocalPhotoCalib")],
467 df[("meas", "g", "base_LocalPhotoCalibErr")])
468 magErr = func.instFluxErrToMagnitudeErr(
469 df[("meas", "g", "base_PsfFlux_instFlux")],
470 df[("meas", "g", "base_PsfFlux_instFluxErr")],
471 df[("meas", "g", "base_LocalPhotoCalib")],
472 df[("meas", "g", "base_LocalPhotoCalibErr")])
474 self.assertTrue(np.allclose(nanoJansky.values,
475 flux * calib,
476 atol=1e-13,
477 rtol=0))
478 self.assertTrue(np.allclose(mag.values,
479 (flux * calib * u.nJy).to_value(u.ABmag),
480 atol=1e-13,
481 rtol=0))
482 self.assertTrue(np.allclose(nanoJanskyErr.values,
483 np.hypot(fluxErr * calib, flux * calibErr),
484 atol=1e-13,
485 rtol=0))
486 self.assertTrue(np.allclose(
487 magErr.values,
488 2.5 / np.log(10) * nanoJanskyErr.values / nanoJansky.values,
489 atol=1e-13,
490 rtol=0))
492 # Test functors against the values computed above.
493 self._testLocalPhotometryFunctors(LocalNanojansky,
494 df,
495 nanoJansky)
496 self._testLocalPhotometryFunctors(LocalNanojanskyErr,
497 df,
498 nanoJanskyErr)
500 def _testLocalPhotometryFunctors(self, functor, df, testValues):
501 func = functor("base_PsfFlux_instFlux",
502 "base_PsfFlux_instFluxErr",
503 "base_LocalPhotoCalib",
504 "base_LocalPhotoCalibErr")
505 val = self._funcVal(func, df)
506 self.assertTrue(np.allclose(testValues.values,
507 val.values,
508 atol=1e-13,
509 rtol=0))
511 def testDipPhotometry(self):
512 """Test calibrated flux calculations for dipoles."""
513 fluxNeg = -100
514 fluxPos = 101
515 fluxErr = 10
516 calib = 10
517 calibErr = 1
519 # compute expected values.
520 absMean = 0.5*(fluxPos - fluxNeg)*calib
521 absDiff = (fluxNeg + fluxPos)*calib
522 absMeanErr = 0.5*np.sqrt(2*(fluxErr*calib)**2
523 + ((fluxPos - fluxNeg)*calibErr)**2)
524 absDiffErr = np.sqrt(2*(fluxErr*calib)**2
525 + ((fluxPos + fluxNeg)*calibErr)**2)
527 self.dataDict["ip_diffim_DipoleFluxNeg_instFlux"] = np.full(self.nRecords, fluxNeg)
528 self.dataDict["ip_diffim_DipoleFluxNeg_instFluxErr"] = np.full(self.nRecords, fluxErr)
529 self.dataDict["ip_diffim_DipoleFluxPos_instFlux"] = np.full(self.nRecords, fluxPos)
530 self.dataDict["ip_diffim_DipoleFluxPos_instFluxErr"] = np.full(self.nRecords, fluxErr)
531 self.dataDict["base_LocalPhotoCalib"] = np.full(self.nRecords, calib)
532 self.dataDict["base_LocalPhotoCalibErr"] = np.full(self.nRecords,
533 calibErr)
535 df = self.getMultiIndexDataFrame(self.dataDict)
536 func = LocalDipoleMeanFlux("ip_diffim_DipoleFluxPos_instFlux",
537 "ip_diffim_DipoleFluxNeg_instFlux",
538 "ip_diffim_DipoleFluxPos_instFluxErr",
539 "ip_diffim_DipoleFluxNeg_instFluxErr",
540 "base_LocalPhotoCalib",
541 "base_LocalPhotoCalibErr")
542 val = self._funcVal(func, df)
543 self.assertTrue(np.allclose(val.values,
544 absMean,
545 atol=1e-13,
546 rtol=0))
548 func = LocalDipoleMeanFluxErr("ip_diffim_DipoleFluxPos_instFlux",
549 "ip_diffim_DipoleFluxNeg_instFlux",
550 "ip_diffim_DipoleFluxPos_instFluxErr",
551 "ip_diffim_DipoleFluxNeg_instFluxErr",
552 "base_LocalPhotoCalib",
553 "base_LocalPhotoCalibErr")
554 val = self._funcVal(func, df)
555 self.assertTrue(np.allclose(val.values,
556 absMeanErr,
557 atol=1e-13,
558 rtol=0))
560 func = LocalDipoleDiffFlux("ip_diffim_DipoleFluxPos_instFlux",
561 "ip_diffim_DipoleFluxNeg_instFlux",
562 "ip_diffim_DipoleFluxPos_instFluxErr",
563 "ip_diffim_DipoleFluxNeg_instFluxErr",
564 "base_LocalPhotoCalib",
565 "base_LocalPhotoCalibErr")
566 val = self._funcVal(func, df)
567 self.assertTrue(np.allclose(val.values,
568 absDiff,
569 atol=1e-13,
570 rtol=0))
572 func = LocalDipoleDiffFluxErr("ip_diffim_DipoleFluxPos_instFlux",
573 "ip_diffim_DipoleFluxNeg_instFlux",
574 "ip_diffim_DipoleFluxPos_instFluxErr",
575 "ip_diffim_DipoleFluxNeg_instFluxErr",
576 "base_LocalPhotoCalib",
577 "base_LocalPhotoCalibErr")
578 val = self._funcVal(func, df)
579 self.assertTrue(np.allclose(val.values,
580 absDiffErr,
581 atol=1e-13,
582 rtol=0))
584 def testConvertPixelToArcseconds(self):
585 """Test calculations of the pixel scale and conversions of pixel to
586 arcseconds.
587 """
588 dipoleSep = 10
589 ixx = 10
590 testPixelDeltas = np.random.uniform(-100, 100, size=(self.nRecords, 2))
591 import lsst.afw.table as afwTable
592 localWcsPlugin = measBase.EvaluateLocalWcsPlugin(
593 None,
594 "base_LocalWcs",
595 afwTable.SourceTable.makeMinimalSchema(),
596 None)
597 for dec in np.linspace(-90, 90, 10):
598 for x, y in zip(np.random.uniform(2 * 1109.99981456774, size=10),
599 np.random.uniform(2 * 560.018167811613, size=10)):
600 center = geom.Point2D(x, y)
601 wcs = self._makeWcs(dec)
602 skyOrigin = wcs.pixelToSky(center)
604 linAffMatrix = localWcsPlugin.makeLocalTransformMatrix(wcs,
605 center)
606 self.dataDict["dipoleSep"] = np.full(self.nRecords, dipoleSep)
607 self.dataDict["ixx"] = np.full(self.nRecords, ixx)
608 self.dataDict["slot_Centroid_x"] = np.full(self.nRecords, x)
609 self.dataDict["slot_Centroid_y"] = np.full(self.nRecords, y)
610 self.dataDict["someCentroid_x"] = x + testPixelDeltas[:, 0]
611 self.dataDict["someCentroid_y"] = y + testPixelDeltas[:, 1]
612 self.dataDict["base_LocalWcs_CDMatrix_1_1"] = np.full(self.nRecords,
613 linAffMatrix[0, 0])
614 self.dataDict["base_LocalWcs_CDMatrix_1_2"] = np.full(self.nRecords,
615 linAffMatrix[0, 1])
616 self.dataDict["base_LocalWcs_CDMatrix_2_1"] = np.full(self.nRecords,
617 linAffMatrix[1, 0])
618 self.dataDict["base_LocalWcs_CDMatrix_2_2"] = np.full(self.nRecords,
619 linAffMatrix[1, 1])
620 df = self.getMultiIndexDataFrame(self.dataDict)
621 func = LocalWcs("base_LocalWcs_CDMatrix_1_1",
622 "base_LocalWcs_CDMatrix_1_2",
623 "base_LocalWcs_CDMatrix_2_1",
624 "base_LocalWcs_CDMatrix_2_2")
626 # Exercise the full set of functions in LocalWcs.
627 sepRadians = func.getSkySeparationFromPixel(
628 df[("meas", "g", "someCentroid_x")] - df[("meas", "g", "slot_Centroid_x")],
629 df[("meas", "g", "someCentroid_y")] - df[("meas", "g", "slot_Centroid_y")],
630 0.0,
631 0.0,
632 df[("meas", "g", "base_LocalWcs_CDMatrix_1_1")],
633 df[("meas", "g", "base_LocalWcs_CDMatrix_1_2")],
634 df[("meas", "g", "base_LocalWcs_CDMatrix_2_1")],
635 df[("meas", "g", "base_LocalWcs_CDMatrix_2_2")])
637 # Test functor values against afw SkyWcs computations.
638 for centX, centY, sep in zip(testPixelDeltas[:, 0],
639 testPixelDeltas[:, 1],
640 sepRadians.values):
641 afwSepRadians = skyOrigin.separation(
642 wcs.pixelToSky(x + centX, y + centY)).asRadians()
643 self.assertAlmostEqual(1 - sep / afwSepRadians, 0, places=6)
645 # Test the pixel scale computation.
646 func = ComputePixelScale("base_LocalWcs_CDMatrix_1_1",
647 "base_LocalWcs_CDMatrix_1_2",
648 "base_LocalWcs_CDMatrix_2_1",
649 "base_LocalWcs_CDMatrix_2_2")
650 pixelScale = self._funcVal(func, df)
651 self.assertTrue(np.allclose(
652 wcs.getPixelScale(center).asArcseconds(),
653 pixelScale.values,
654 rtol=1e-8,
655 atol=0))
657 # Test pixel -> arcsec conversion.
658 func = ConvertPixelToArcseconds("dipoleSep",
659 "base_LocalWcs_CDMatrix_1_1",
660 "base_LocalWcs_CDMatrix_1_2",
661 "base_LocalWcs_CDMatrix_2_1",
662 "base_LocalWcs_CDMatrix_2_2")
663 val = self._funcVal(func, df)
664 self.assertTrue(np.allclose(pixelScale.values * dipoleSep,
665 val.values,
666 atol=1e-16,
667 rtol=1e-16))
669 # Test pixel^2 -> arcsec^2 conversion.
670 func = ConvertPixelSqToArcsecondsSq("ixx",
671 "base_LocalWcs_CDMatrix_1_1",
672 "base_LocalWcs_CDMatrix_1_2",
673 "base_LocalWcs_CDMatrix_2_1",
674 "base_LocalWcs_CDMatrix_2_2")
675 val = self._funcVal(func, df)
676 self.assertTrue(np.allclose(pixelScale.values ** 2 * dipoleSep,
677 val.values,
678 atol=1e-16,
679 rtol=1e-16))
681 def _makeWcs(self, dec=53.1595451514076):
682 """Create a wcs from real CFHT values.
684 Returns
685 -------
686 wcs : `lsst.afw.geom`
687 Created wcs.
688 """
689 metadata = dafBase.PropertySet()
691 metadata.set("SIMPLE", "T")
692 metadata.set("BITPIX", -32)
693 metadata.set("NAXIS", 2)
694 metadata.set("NAXIS1", 1024)
695 metadata.set("NAXIS2", 1153)
696 metadata.set("RADECSYS", 'FK5')
697 metadata.set("EQUINOX", 2000.)
699 metadata.setDouble("CRVAL1", 215.604025685476)
700 metadata.setDouble("CRVAL2", dec)
701 metadata.setDouble("CRPIX1", 1109.99981456774)
702 metadata.setDouble("CRPIX2", 560.018167811613)
703 metadata.set("CTYPE1", 'RA---SIN')
704 metadata.set("CTYPE2", 'DEC--SIN')
706 metadata.setDouble("CD1_1", 5.10808596133527E-05)
707 metadata.setDouble("CD1_2", 1.85579539217196E-07)
708 metadata.setDouble("CD2_2", -5.10281493481982E-05)
709 metadata.setDouble("CD2_1", -8.27440751733828E-07)
711 return afwGeom.makeSkyWcs(metadata)
713 def testHtm(self):
714 """Test that HtmIndxes are created as expected.
715 """
716 df = self.getMultiIndexDataFrame(self.dataDict)
717 func = HtmIndex20("coord_ra", "coord_dec")
719 val = self._funcVal(func, df)
720 # Test that the HtmIds come out as the ra/dec in dataDict.
721 self.assertTrue(np.all(np.equal(
722 val.values,
723 [14924528684992, 14924528689697, 14924528501716, 14924526434259,
724 14924526433879])))
726 def testEbv(self):
727 """Test that EBV works.
728 """
729 df = self.getMultiIndexDataFrame(self.dataDict)
730 func = Ebv()
732 val = self._funcVal(func, df)
733 np.testing.assert_array_almost_equal(
734 val.values,
735 [0.029100, 0.029013, 0.028857, 0.028802, 0.028797]
736 )
738 def _dropLevels(self, df):
739 levelsToDrop = [n for lev, n in zip(df.columns.levels, df.columns.names) if len(lev) == 1]
741 # Prevent error when trying to drop *all* columns
742 if len(levelsToDrop) == len(df.columns.names):
743 levelsToDrop.remove(df.columns.names[-1])
745 df.columns = df.columns.droplevel(levelsToDrop)
748class MyMemoryTestCase(lsst.utils.tests.MemoryTestCase):
749 pass
752def setup_module(module):
753 lsst.utils.tests.init()
756if __name__ == "__main__": 756 ↛ 757line 756 didn't jump to line 757, because the condition on line 756 was never true
757 lsst.utils.tests.init()
758 unittest.main()