Coverage for tests/test_functors.py: 13%
396 statements
« prev ^ index » next coverage.py v7.2.5, created at 2023-05-17 03:36 -0700
« prev ^ index » next coverage.py v7.2.5, created at 2023-05-17 03:36 -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, StarGalaxyLabeller,
40 DeconvolvedMoments, SdssTraceSize, PsfSdssTraceSizeDiff,
41 HsmTraceSize, PsfHsmTraceSizeDiff, HsmFwhm,
42 LocalPhotometry, LocalNanojansky, LocalNanojanskyErr,
43 LocalMagnitude, LocalMagnitudeErr,
44 LocalDipoleMeanFlux, LocalDipoleMeanFluxErr,
45 LocalDipoleDiffFlux, LocalDipoleDiffFluxErr,
46 LocalWcs, ComputePixelScale, ConvertPixelToArcseconds,
47 ConvertPixelSqToArcsecondsSq, Ratio, HtmIndex20, Ebv)
49ROOT = os.path.abspath(os.path.dirname(__file__))
52class FunctorTestCase(unittest.TestCase):
54 def getMultiIndexDataFrame(self, dataDict):
55 """Create a simple test multi-index DataFrame."""
57 simpleDF = pd.DataFrame(dataDict)
58 dfFilterDSCombos = []
59 for ds in self.datasets:
60 for band in self.bands:
61 df = copy.copy(simpleDF)
62 df.reindex(sorted(df.columns), axis=1)
63 df['dataset'] = ds
64 df['band'] = band
65 df.columns = pd.MultiIndex.from_tuples(
66 [(ds, band, c) for c in df.columns],
67 names=('dataset', 'band', 'column'))
68 dfFilterDSCombos.append(df)
70 df = functools.reduce(lambda d1, d2: d1.join(d2), dfFilterDSCombos)
72 return df
74 def getSimpleDataFrame(self, dataDict):
75 return pd.DataFrame(dataDict)
77 def getDatasetHandle(self, df):
78 lo, hi = HtmPixelization(7).universe().ranges()[0]
79 value = np.random.randint(lo, hi)
80 return InMemoryDatasetHandle(df, storageClass="DataFrame", dataId={"htm7": value})
82 def setUp(self):
83 np.random.seed(12345)
84 self.datasets = ['forced_src', 'meas', 'ref']
85 self.bands = ['g', 'r']
86 self.columns = ['coord_ra', 'coord_dec']
87 self.nRecords = 5
88 self.dataDict = {
89 "coord_ra": [3.77654137, 3.77643059, 3.77621148, 3.77611944, 3.77610396],
90 "coord_dec": [0.01127624, 0.01127787, 0.01127543, 0.01127543, 0.01127543]}
92 def _funcVal(self, functor, df):
93 self.assertIsInstance(functor.name, str)
94 self.assertIsInstance(functor.shortname, str)
96 handle = self.getDatasetHandle(df)
98 val = functor(df)
99 val2 = functor(handle)
100 self.assertTrue((val == val2).all())
101 self.assertIsInstance(val, pd.Series)
103 val = functor(df, dropna=True)
104 val2 = functor(handle, dropna=True)
105 self.assertTrue((val == val2).all())
106 self.assertEqual(val.isnull().sum(), 0)
108 return val
110 def _differenceVal(self, functor, df1, df2):
111 self.assertIsInstance(functor.name, str)
112 self.assertIsInstance(functor.shortname, str)
114 handle1 = self.getDatasetHandle(df1)
115 handle2 = self.getDatasetHandle(df2)
117 val = functor.difference(df1, df2)
118 val2 = functor.difference(handle1, handle2)
119 self.assertTrue(val.equals(val2))
120 self.assertIsInstance(val, pd.Series)
122 val = functor.difference(df1, df2, dropna=True)
123 val2 = functor.difference(handle1, handle2, dropna=True)
124 self.assertTrue(val.equals(val2))
125 self.assertEqual(val.isnull().sum(), 0)
127 val1 = self._funcVal(functor, df1)
128 val2 = self._funcVal(functor, df2)
130 self.assertTrue(np.allclose(val, val1 - val2))
132 return val
134 def testColumn(self):
135 self.columns.append("base_FootprintArea_value")
136 self.dataDict["base_FootprintArea_value"] = \
137 np.full(self.nRecords, 1)
138 df = self.getMultiIndexDataFrame(self.dataDict)
139 func = Column('base_FootprintArea_value', filt='g')
140 self._funcVal(func, df)
142 df = self.getSimpleDataFrame(self.dataDict)
143 func = Column('base_FootprintArea_value')
144 self._funcVal(func, df)
146 def testCustom(self):
147 self.columns.append("base_FootprintArea_value")
148 self.dataDict["base_FootprintArea_value"] = \
149 np.random.rand(self.nRecords)
150 df = self.getMultiIndexDataFrame(self.dataDict)
151 func = CustomFunctor('2*base_FootprintArea_value', filt='g')
152 val = self._funcVal(func, df)
154 func2 = Column('base_FootprintArea_value', filt='g')
156 np.allclose(val.values, 2*func2(df).values, atol=1e-13, rtol=0)
158 df = self.getSimpleDataFrame(self.dataDict)
159 func = CustomFunctor('2 * base_FootprintArea_value')
160 val = self._funcVal(func, df)
161 func2 = Column('base_FootprintArea_value')
163 np.allclose(val.values, 2*func2(df).values, atol=1e-13, rtol=0)
165 def testCoords(self):
166 df = self.getMultiIndexDataFrame(self.dataDict)
167 ra = self._funcVal(RAColumn(), df)
168 dec = self._funcVal(DecColumn(), df)
170 columnDict = {'dataset': 'ref', 'band': 'g',
171 'column': ['coord_ra', 'coord_dec']}
173 handle = InMemoryDatasetHandle(df, storageClass="DataFrame")
174 dfSub = handle.get(parameters={"columns": columnDict})
175 self._dropLevels(dfSub)
177 coords = dfSub / np.pi * 180.
179 self.assertTrue(np.allclose(ra, coords[('ref', 'g', 'coord_ra')], atol=1e-13, rtol=0))
180 self.assertTrue(np.allclose(dec, coords[('ref', 'g', 'coord_dec')], atol=1e-13, rtol=0))
182 # single-level column index table
183 df = self.getSimpleDataFrame(self.dataDict)
184 ra = self._funcVal(RAColumn(), df)
185 dec = self._funcVal(DecColumn(), df)
187 handle = InMemoryDatasetHandle(df, storageClass="DataFrame")
188 dfSub = handle.get(parameters={"columns": ['coord_ra', 'coord_dec']})
189 coords = dfSub / np.pi * 180.
191 self.assertTrue(np.allclose(ra, coords['coord_ra'], atol=1e-13, rtol=0))
192 self.assertTrue(np.allclose(dec, coords['coord_dec'], atol=1e-13, rtol=0))
194 def testMag(self):
195 self.columns.extend(["base_PsfFlux_instFlux", "base_PsfFlux_instFluxErr"])
196 self.dataDict["base_PsfFlux_instFlux"] = np.full(self.nRecords, 1000)
197 self.dataDict["base_PsfFlux_instFluxErr"] = np.full(self.nRecords, 10)
198 df = self.getMultiIndexDataFrame(self.dataDict)
199 # Change one dataset filter combinations value.
200 df[("meas", "g", "base_PsfFlux_instFlux")] -= 1
202 fluxName = 'base_PsfFlux'
204 # Check that things work when you provide dataset explicitly
205 for dataset in ['forced_src', 'meas']:
206 psfMag_G = self._funcVal(Mag(fluxName, dataset=dataset,
207 filt='g'),
208 df)
209 psfMag_R = self._funcVal(Mag(fluxName, dataset=dataset,
210 filt='r'),
211 df)
213 psfColor_GR = self._funcVal(Color(fluxName, 'g', 'r',
214 dataset=dataset),
215 df)
217 self.assertTrue(np.allclose((psfMag_G - psfMag_R).dropna(), psfColor_GR, rtol=0, atol=1e-13))
219 # Check that behavior as expected when dataset not provided;
220 # that is, that the color comes from forced and default Mag is meas
221 psfMag_G = self._funcVal(Mag(fluxName, filt='g'), df)
222 psfMag_R = self._funcVal(Mag(fluxName, filt='r'), df)
224 psfColor_GR = self._funcVal(Color(fluxName, 'g', 'r'), df)
226 # These should *not* be equal.
227 self.assertFalse(np.allclose((psfMag_G - psfMag_R).dropna(), psfColor_GR))
229 def testMagDiff(self):
230 self.columns.extend(["base_PsfFlux_instFlux", "base_PsfFlux_instFluxErr",
231 "modelfit_CModel_instFlux", "modelfit_CModel_instFluxErr"])
232 self.dataDict["base_PsfFlux_instFlux"] = np.full(self.nRecords, 1000)
233 self.dataDict["base_PsfFlux_instFluxErr"] = np.full(self.nRecords, 10)
234 self.dataDict["modelfit_CModel_instFlux"] = np.full(self.nRecords, 1000)
235 self.dataDict["modelfit_CModel_instFluxErr"] = np.full(self.nRecords, 10)
236 df = self.getMultiIndexDataFrame(self.dataDict)
238 for filt in self.bands:
239 filt = 'g'
240 val = self._funcVal(MagDiff('base_PsfFlux', 'modelfit_CModel', filt=filt), df)
242 mag1 = self._funcVal(Mag('modelfit_CModel', filt=filt), df)
243 mag2 = self._funcVal(Mag('base_PsfFlux', filt=filt), df)
244 self.assertTrue(np.allclose((mag2 - mag1).dropna(), val, rtol=0, atol=1e-13))
246 def testDifference(self):
247 """Test .difference method using MagDiff as the example.
248 """
249 self.columns.extend(["base_PsfFlux_instFlux", "base_PsfFlux_instFluxErr",
250 "modelfit_CModel_instFlux", "modelfit_CModel_instFluxErr"])
252 self.dataDict["base_PsfFlux_instFlux"] = np.full(self.nRecords, 1000)
253 self.dataDict["modelfit_CModel_instFlux"] = np.full(self.nRecords, 1000)
254 df1 = self.getMultiIndexDataFrame(self.dataDict)
256 self.dataDict["base_PsfFlux_instFlux"] = np.full(self.nRecords, 999)
257 self.dataDict["modelfit_CModel_instFlux"] = np.full(self.nRecords, 999)
258 df2 = self.getMultiIndexDataFrame(self.dataDict)
260 magDiff = MagDiff('base_PsfFlux', 'modelfit_CModel', filt='g')
262 # Asserts that differences computed properly
263 self._differenceVal(magDiff, df1, df2)
265 def testLabeller(self):
266 # Covering the code is better than nothing
267 self.columns.append("base_ClassificationExtendedness_value")
268 self.dataDict["base_ClassificationExtendedness_value"] = np.full(self.nRecords, 1)
269 df = self.getMultiIndexDataFrame(self.dataDict)
270 _ = self._funcVal(StarGalaxyLabeller(), df)
272 def testPixelScale(self):
273 # Test that the pixel scale and pix->arcsec calculations perform as
274 # expected.
275 pass
277 def testOther(self):
278 self.columns.extend(["ext_shapeHSM_HsmSourceMoments_xx", "ext_shapeHSM_HsmSourceMoments_yy",
279 "base_SdssShape_xx", "base_SdssShape_yy",
280 "ext_shapeHSM_HsmPsfMoments_xx", "ext_shapeHSM_HsmPsfMoments_yy",
281 "base_SdssShape_psf_xx", "base_SdssShape_psf_yy"])
282 self.dataDict["ext_shapeHSM_HsmSourceMoments_xx"] = np.full(self.nRecords, 1 / np.sqrt(2))
283 self.dataDict["ext_shapeHSM_HsmSourceMoments_yy"] = np.full(self.nRecords, 1 / np.sqrt(2))
284 self.dataDict["base_SdssShape_xx"] = np.full(self.nRecords, 1 / np.sqrt(2))
285 self.dataDict["base_SdssShape_yy"] = np.full(self.nRecords, 1 / np.sqrt(2))
286 self.dataDict["ext_shapeHSM_HsmPsfMoments_xx"] = np.full(self.nRecords, 1 / np.sqrt(2))
287 self.dataDict["ext_shapeHSM_HsmPsfMoments_yy"] = np.full(self.nRecords, 1 / np.sqrt(2))
288 self.dataDict["base_SdssShape_psf_xx"] = np.full(self.nRecords, 1)
289 self.dataDict["base_SdssShape_psf_yy"] = np.full(self.nRecords, 1)
290 df = self.getMultiIndexDataFrame(self.dataDict)
291 # Covering the code is better than nothing
292 for filt in self.bands:
293 for Func in [DeconvolvedMoments,
294 SdssTraceSize,
295 PsfSdssTraceSizeDiff,
296 HsmTraceSize, PsfHsmTraceSizeDiff, HsmFwhm]:
297 _ = self._funcVal(Func(filt=filt), df)
299 def _compositeFuncVal(self, functor, df):
300 self.assertIsInstance(functor, CompositeFunctor)
302 handle = self.getDatasetHandle(df)
304 fdf1 = functor(df)
305 fdf2 = functor(handle)
306 self.assertTrue(fdf1.equals(fdf2))
308 self.assertIsInstance(fdf1, pd.DataFrame)
309 self.assertTrue(np.all([k in fdf1.columns for k in functor.funcDict.keys()]))
311 fdf1 = functor(df, dropna=True)
312 fdf2 = functor(handle, dropna=True)
313 self.assertTrue(fdf1.equals(fdf2))
315 # Check that there are no nulls
316 self.assertFalse(fdf1.isnull().any(axis=None))
318 return fdf1
320 def _compositeDifferenceVal(self, functor, df1, df2):
321 self.assertIsInstance(functor, CompositeFunctor)
323 handle1 = self.getDatasetHandle(df1)
324 handle2 = self.getDatasetHandle(df2)
326 fdf1 = functor.difference(df1, df2)
327 fdf2 = functor.difference(handle1, handle2)
328 self.assertTrue(fdf1.equals(fdf2))
330 self.assertIsInstance(fdf1, pd.DataFrame)
331 self.assertTrue(np.all([k in fdf1.columns for k in functor.funcDict.keys()]))
333 fdf1 = functor.difference(df1, df2, dropna=True)
334 fdf2 = functor.difference(handle1, handle2, dropna=True)
335 self.assertTrue(fdf1.equals(fdf2))
337 # Check that there are no nulls
338 self.assertFalse(fdf1.isnull().any(axis=None))
340 df1_functored = functor(df1)
341 df2_functored = functor(df2)
343 self.assertTrue(np.allclose(fdf1.values, df1_functored.values - df2_functored.values))
345 return fdf1
347 def testComposite(self):
348 self.columns.extend(["modelfit_CModel_instFlux", "base_PsfFlux_instFlux"])
349 self.dataDict["modelfit_CModel_instFlux"] = np.full(self.nRecords, 1)
350 self.dataDict["base_PsfFlux_instFlux"] = np.full(self.nRecords, 1)
352 df = self.getMultiIndexDataFrame(self.dataDict)
353 # Modify r band value slightly.
354 df[("meas", "r", "base_PsfFlux_instFlux")] -= 0.1
356 filt = 'g'
357 funcDict = {'psfMag_ref': Mag('base_PsfFlux', dataset='ref'),
358 'ra': RAColumn(),
359 'dec': DecColumn(),
360 'psfMag': Mag('base_PsfFlux', filt=filt),
361 'cmodel_magDiff': MagDiff('base_PsfFlux',
362 'modelfit_CModel', filt=filt)}
363 func = CompositeFunctor(funcDict)
364 fdf1 = self._compositeFuncVal(func, df)
366 # Repeat same, but define filter globally instead of individually
367 funcDict2 = {'psfMag_ref': Mag('base_PsfFlux', dataset='ref'),
368 'ra': RAColumn(),
369 'dec': DecColumn(),
370 'psfMag': Mag('base_PsfFlux'),
371 'cmodel_magDiff': MagDiff('base_PsfFlux',
372 'modelfit_CModel')}
374 func2 = CompositeFunctor(funcDict2, filt=filt)
375 fdf2 = self._compositeFuncVal(func2, df)
376 self.assertTrue(fdf1.equals(fdf2))
378 func2.filt = 'r'
379 fdf3 = self._compositeFuncVal(func2, df)
380 # Because we modified the R filter this should fail.
381 self.assertFalse(fdf2.equals(fdf3))
383 # Make sure things work with passing list instead of dict
384 funcs = [Mag('base_PsfFlux', dataset='ref'),
385 RAColumn(),
386 DecColumn(),
387 Mag('base_PsfFlux', filt=filt),
388 MagDiff('base_PsfFlux', 'modelfit_CModel', filt=filt)]
390 _ = self._compositeFuncVal(CompositeFunctor(funcs), df)
392 def testCompositeSimple(self):
393 """Test single-level composite functor for functionality
394 """
395 self.columns.extend(["modelfit_CModel_instFlux", "base_PsfFlux_instFlux"])
396 self.dataDict["modelfit_CModel_instFlux"] = np.full(self.nRecords, 1)
397 self.dataDict["base_PsfFlux_instFlux"] = np.full(self.nRecords, 1)
399 df = self.getSimpleDataFrame(self.dataDict)
400 funcDict = {'ra': RAColumn(),
401 'dec': DecColumn(),
402 'psfMag': Mag('base_PsfFlux'),
403 'cmodel_magDiff': MagDiff('base_PsfFlux',
404 'modelfit_CModel')}
405 func = CompositeFunctor(funcDict)
406 _ = self._compositeFuncVal(func, df)
408 def testCompositeColor(self):
409 self.dataDict["base_PsfFlux_instFlux"] = np.full(self.nRecords, 1000)
410 self.dataDict["base_PsfFlux_instFluxErr"] = np.full(self.nRecords, 10)
411 df = self.getMultiIndexDataFrame(self.dataDict)
412 funcDict = {'a': Mag('base_PsfFlux', dataset='meas', filt='g'),
413 'b': Mag('base_PsfFlux', dataset='forced_src', filt='g'),
414 'c': Color('base_PsfFlux', 'g', 'r')}
415 # Covering the code is better than nothing
416 _ = self._compositeFuncVal(CompositeFunctor(funcDict), df)
418 def testCompositeDifference(self):
419 self.dataDict["base_PsfFlux_instFlux"] = np.full(self.nRecords, 1000)
420 self.dataDict["base_PsfFlux_instFluxErr"] = np.full(self.nRecords, 10)
421 df1 = self.getMultiIndexDataFrame(self.dataDict)
423 self.dataDict["base_PsfFlux_instFlux"] = np.full(self.nRecords, 999)
424 self.dataDict["base_PsfFlux_instFluxErr"] = np.full(self.nRecords, 9)
425 df2 = self.getMultiIndexDataFrame(self.dataDict)
427 funcDict = {'a': Mag('base_PsfFlux', dataset='meas', filt='g'),
428 'b': Mag('base_PsfFlux', dataset='forced_src', filt='g'),
429 'c': Color('base_PsfFlux', 'g', 'r')}
430 # Covering the code is better than nothing
431 _ = self._compositeDifferenceVal(CompositeFunctor(funcDict), df1, df2)
433 def testCompositeFail(self):
434 """Test a composite functor where one of the functors should be junk.
435 """
436 self.dataDict["base_PsfFlux_instFlux"] = np.full(self.nRecords, 1000)
437 df = self.getMultiIndexDataFrame(self.dataDict)
439 funcDict = {'good': Column("base_PsfFlux_instFlux"),
440 'bad': Column('not_a_column')}
442 with self.assertLogs(level=logging.ERROR) as cm:
443 _ = self._compositeFuncVal(CompositeFunctor(funcDict), df)
444 self.assertIn("Exception in CompositeFunctor (funcs: ['good', 'bad'])", cm.output[0])
446 def testLocalPhotometry(self):
447 """Test the local photometry functors.
448 """
449 flux = 1000
450 fluxErr = 10
451 calib = 10
452 calibErr = 1
453 self.dataDict["base_PsfFlux_instFlux"] = np.full(self.nRecords, flux)
454 self.dataDict["base_PsfFlux_instFluxErr"] = np.full(self.nRecords,
455 fluxErr)
456 self.dataDict["base_LocalPhotoCalib"] = np.full(self.nRecords, calib)
457 self.dataDict["base_LocalPhotoCalibErr"] = np.full(self.nRecords,
458 calibErr)
459 df = self.getMultiIndexDataFrame(self.dataDict)
460 func = LocalPhotometry("base_PsfFlux_instFlux",
461 "base_PsfFlux_instFluxErr",
462 "base_LocalPhotoCalib",
463 "base_LocalPhotoCalibErr")
465 nanoJansky = func.instFluxToNanojansky(
466 df[("meas", "g", "base_PsfFlux_instFlux")],
467 df[("meas", "g", "base_LocalPhotoCalib")])
468 mag = func.instFluxToMagnitude(
469 df[("meas", "g", "base_PsfFlux_instFlux")],
470 df[("meas", "g", "base_LocalPhotoCalib")])
471 nanoJanskyErr = func.instFluxErrToNanojanskyErr(
472 df[("meas", "g", "base_PsfFlux_instFlux")],
473 df[("meas", "g", "base_PsfFlux_instFluxErr")],
474 df[("meas", "g", "base_LocalPhotoCalib")],
475 df[("meas", "g", "base_LocalPhotoCalibErr")])
476 magErr = func.instFluxErrToMagnitudeErr(
477 df[("meas", "g", "base_PsfFlux_instFlux")],
478 df[("meas", "g", "base_PsfFlux_instFluxErr")],
479 df[("meas", "g", "base_LocalPhotoCalib")],
480 df[("meas", "g", "base_LocalPhotoCalibErr")])
482 self.assertTrue(np.allclose(nanoJansky.values,
483 flux * calib,
484 atol=1e-13,
485 rtol=0))
486 self.assertTrue(np.allclose(mag.values,
487 (flux * calib * u.nJy).to_value(u.ABmag),
488 atol=1e-13,
489 rtol=0))
490 self.assertTrue(np.allclose(nanoJanskyErr.values,
491 np.hypot(fluxErr * calib, flux * calibErr),
492 atol=1e-13,
493 rtol=0))
494 self.assertTrue(np.allclose(
495 magErr.values,
496 2.5 / np.log(10) * nanoJanskyErr.values / nanoJansky.values,
497 atol=1e-13,
498 rtol=0))
500 # Test functors against the values computed above.
501 self._testLocalPhotometryFunctors(LocalNanojansky,
502 df,
503 nanoJansky)
504 self._testLocalPhotometryFunctors(LocalNanojanskyErr,
505 df,
506 nanoJanskyErr)
507 self._testLocalPhotometryFunctors(LocalMagnitude,
508 df,
509 mag)
510 self._testLocalPhotometryFunctors(LocalMagnitudeErr,
511 df,
512 magErr)
514 def _testLocalPhotometryFunctors(self, functor, df, testValues):
515 func = functor("base_PsfFlux_instFlux",
516 "base_PsfFlux_instFluxErr",
517 "base_LocalPhotoCalib",
518 "base_LocalPhotoCalibErr")
519 val = self._funcVal(func, df)
520 self.assertTrue(np.allclose(testValues.values,
521 val.values,
522 atol=1e-13,
523 rtol=0))
525 def testDipPhotometry(self):
526 """Test calibrated flux calculations for dipoles."""
527 fluxNeg = -100
528 fluxPos = 101
529 fluxErr = 10
530 calib = 10
531 calibErr = 1
533 # compute expected values.
534 absMean = 0.5*(fluxPos - fluxNeg)*calib
535 absDiff = (fluxNeg + fluxPos)*calib
536 absMeanErr = 0.5*np.sqrt(2*(fluxErr*calib)**2
537 + ((fluxPos - fluxNeg)*calibErr)**2)
538 absDiffErr = np.sqrt(2*(fluxErr*calib)**2
539 + ((fluxPos + fluxNeg)*calibErr)**2)
541 self.dataDict["ip_diffim_DipoleFluxNeg_instFlux"] = np.full(self.nRecords, fluxNeg)
542 self.dataDict["ip_diffim_DipoleFluxNeg_instFluxErr"] = np.full(self.nRecords, fluxErr)
543 self.dataDict["ip_diffim_DipoleFluxPos_instFlux"] = np.full(self.nRecords, fluxPos)
544 self.dataDict["ip_diffim_DipoleFluxPos_instFluxErr"] = np.full(self.nRecords, fluxErr)
545 self.dataDict["base_LocalPhotoCalib"] = np.full(self.nRecords, calib)
546 self.dataDict["base_LocalPhotoCalibErr"] = np.full(self.nRecords,
547 calibErr)
549 df = self.getMultiIndexDataFrame(self.dataDict)
550 func = LocalDipoleMeanFlux("ip_diffim_DipoleFluxPos_instFlux",
551 "ip_diffim_DipoleFluxNeg_instFlux",
552 "ip_diffim_DipoleFluxPos_instFluxErr",
553 "ip_diffim_DipoleFluxNeg_instFluxErr",
554 "base_LocalPhotoCalib",
555 "base_LocalPhotoCalibErr")
556 val = self._funcVal(func, df)
557 self.assertTrue(np.allclose(val.values,
558 absMean,
559 atol=1e-13,
560 rtol=0))
562 func = LocalDipoleMeanFluxErr("ip_diffim_DipoleFluxPos_instFlux",
563 "ip_diffim_DipoleFluxNeg_instFlux",
564 "ip_diffim_DipoleFluxPos_instFluxErr",
565 "ip_diffim_DipoleFluxNeg_instFluxErr",
566 "base_LocalPhotoCalib",
567 "base_LocalPhotoCalibErr")
568 val = self._funcVal(func, df)
569 self.assertTrue(np.allclose(val.values,
570 absMeanErr,
571 atol=1e-13,
572 rtol=0))
574 func = LocalDipoleDiffFlux("ip_diffim_DipoleFluxPos_instFlux",
575 "ip_diffim_DipoleFluxNeg_instFlux",
576 "ip_diffim_DipoleFluxPos_instFluxErr",
577 "ip_diffim_DipoleFluxNeg_instFluxErr",
578 "base_LocalPhotoCalib",
579 "base_LocalPhotoCalibErr")
580 val = self._funcVal(func, df)
581 self.assertTrue(np.allclose(val.values,
582 absDiff,
583 atol=1e-13,
584 rtol=0))
586 func = LocalDipoleDiffFluxErr("ip_diffim_DipoleFluxPos_instFlux",
587 "ip_diffim_DipoleFluxNeg_instFlux",
588 "ip_diffim_DipoleFluxPos_instFluxErr",
589 "ip_diffim_DipoleFluxNeg_instFluxErr",
590 "base_LocalPhotoCalib",
591 "base_LocalPhotoCalibErr")
592 val = self._funcVal(func, df)
593 self.assertTrue(np.allclose(val.values,
594 absDiffErr,
595 atol=1e-13,
596 rtol=0))
598 def testConvertPixelToArcseconds(self):
599 """Test calculations of the pixel scale and conversions of pixel to
600 arcseconds.
601 """
602 dipoleSep = 10
603 ixx = 10
604 testPixelDeltas = np.random.uniform(-100, 100, size=(self.nRecords, 2))
605 import lsst.afw.table as afwTable
606 localWcsPlugin = measBase.EvaluateLocalWcsPlugin(
607 None,
608 "base_LocalWcs",
609 afwTable.SourceTable.makeMinimalSchema(),
610 None)
611 for dec in np.linspace(-90, 90, 10):
612 for x, y in zip(np.random.uniform(2 * 1109.99981456774, size=10),
613 np.random.uniform(2 * 560.018167811613, size=10)):
614 center = geom.Point2D(x, y)
615 wcs = self._makeWcs(dec)
616 skyOrigin = wcs.pixelToSky(center)
618 linAffMatrix = localWcsPlugin.makeLocalTransformMatrix(wcs,
619 center)
620 self.dataDict["dipoleSep"] = np.full(self.nRecords, dipoleSep)
621 self.dataDict["ixx"] = np.full(self.nRecords, ixx)
622 self.dataDict["slot_Centroid_x"] = np.full(self.nRecords, x)
623 self.dataDict["slot_Centroid_y"] = np.full(self.nRecords, y)
624 self.dataDict["someCentroid_x"] = x + testPixelDeltas[:, 0]
625 self.dataDict["someCentroid_y"] = y + testPixelDeltas[:, 1]
626 self.dataDict["base_LocalWcs_CDMatrix_1_1"] = np.full(self.nRecords,
627 linAffMatrix[0, 0])
628 self.dataDict["base_LocalWcs_CDMatrix_1_2"] = np.full(self.nRecords,
629 linAffMatrix[0, 1])
630 self.dataDict["base_LocalWcs_CDMatrix_2_1"] = np.full(self.nRecords,
631 linAffMatrix[1, 0])
632 self.dataDict["base_LocalWcs_CDMatrix_2_2"] = np.full(self.nRecords,
633 linAffMatrix[1, 1])
634 df = self.getMultiIndexDataFrame(self.dataDict)
635 func = LocalWcs("base_LocalWcs_CDMatrix_1_1",
636 "base_LocalWcs_CDMatrix_1_2",
637 "base_LocalWcs_CDMatrix_2_1",
638 "base_LocalWcs_CDMatrix_2_2")
640 # Exercise the full set of functions in LocalWcs.
641 sepRadians = func.getSkySeparationFromPixel(
642 df[("meas", "g", "someCentroid_x")] - df[("meas", "g", "slot_Centroid_x")],
643 df[("meas", "g", "someCentroid_y")] - df[("meas", "g", "slot_Centroid_y")],
644 0.0,
645 0.0,
646 df[("meas", "g", "base_LocalWcs_CDMatrix_1_1")],
647 df[("meas", "g", "base_LocalWcs_CDMatrix_1_2")],
648 df[("meas", "g", "base_LocalWcs_CDMatrix_2_1")],
649 df[("meas", "g", "base_LocalWcs_CDMatrix_2_2")])
651 # Test functor values against afw SkyWcs computations.
652 for centX, centY, sep in zip(testPixelDeltas[:, 0],
653 testPixelDeltas[:, 1],
654 sepRadians.values):
655 afwSepRadians = skyOrigin.separation(
656 wcs.pixelToSky(x + centX, y + centY)).asRadians()
657 self.assertAlmostEqual(1 - sep / afwSepRadians, 0, places=6)
659 # Test the pixel scale computation.
660 func = ComputePixelScale("base_LocalWcs_CDMatrix_1_1",
661 "base_LocalWcs_CDMatrix_1_2",
662 "base_LocalWcs_CDMatrix_2_1",
663 "base_LocalWcs_CDMatrix_2_2")
664 pixelScale = self._funcVal(func, df)
665 self.assertTrue(np.allclose(
666 wcs.getPixelScale(center).asArcseconds(),
667 pixelScale.values,
668 rtol=1e-8,
669 atol=0))
671 # Test pixel -> arcsec conversion.
672 func = ConvertPixelToArcseconds("dipoleSep",
673 "base_LocalWcs_CDMatrix_1_1",
674 "base_LocalWcs_CDMatrix_1_2",
675 "base_LocalWcs_CDMatrix_2_1",
676 "base_LocalWcs_CDMatrix_2_2")
677 val = self._funcVal(func, df)
678 self.assertTrue(np.allclose(pixelScale.values * dipoleSep,
679 val.values,
680 atol=1e-16,
681 rtol=1e-16))
683 # Test pixel^2 -> arcsec^2 conversion.
684 func = ConvertPixelSqToArcsecondsSq("ixx",
685 "base_LocalWcs_CDMatrix_1_1",
686 "base_LocalWcs_CDMatrix_1_2",
687 "base_LocalWcs_CDMatrix_2_1",
688 "base_LocalWcs_CDMatrix_2_2")
689 val = self._funcVal(func, df)
690 self.assertTrue(np.allclose(pixelScale.values ** 2 * dipoleSep,
691 val.values,
692 atol=1e-16,
693 rtol=1e-16))
695 def _makeWcs(self, dec=53.1595451514076):
696 """Create a wcs from real CFHT values.
698 Returns
699 -------
700 wcs : `lsst.afw.geom`
701 Created wcs.
702 """
703 metadata = dafBase.PropertySet()
705 metadata.set("SIMPLE", "T")
706 metadata.set("BITPIX", -32)
707 metadata.set("NAXIS", 2)
708 metadata.set("NAXIS1", 1024)
709 metadata.set("NAXIS2", 1153)
710 metadata.set("RADECSYS", 'FK5')
711 metadata.set("EQUINOX", 2000.)
713 metadata.setDouble("CRVAL1", 215.604025685476)
714 metadata.setDouble("CRVAL2", dec)
715 metadata.setDouble("CRPIX1", 1109.99981456774)
716 metadata.setDouble("CRPIX2", 560.018167811613)
717 metadata.set("CTYPE1", 'RA---SIN')
718 metadata.set("CTYPE2", 'DEC--SIN')
720 metadata.setDouble("CD1_1", 5.10808596133527E-05)
721 metadata.setDouble("CD1_2", 1.85579539217196E-07)
722 metadata.setDouble("CD2_2", -5.10281493481982E-05)
723 metadata.setDouble("CD2_1", -8.27440751733828E-07)
725 return afwGeom.makeSkyWcs(metadata)
727 def testRatio(self):
728 """Test the ratio functor where one of the functors should be junk.
729 """
730 self.dataDict["base_PsfFlux_instFlux"] = np.full(self.nRecords, 1000)
731 self.dataDict["base_PsfFlux_instFluxErr"] = np.full(self.nRecords, 100)
732 df = self.getMultiIndexDataFrame(self.dataDict)
734 func = Ratio("base_PsfFlux_instFlux", "base_PsfFlux_instFluxErr")
736 val = self._funcVal(func, df)
737 self.assertTrue(np.allclose(np.full(self.nRecords, 10),
738 val.values,
739 atol=1e-16,
740 rtol=1e-16))
742 def testHtm(self):
743 """Test that HtmIndxes are created as expected.
744 """
745 df = self.getMultiIndexDataFrame(self.dataDict)
746 func = HtmIndex20("coord_ra", "coord_dec")
748 val = self._funcVal(func, df)
749 # Test that the HtmIds come out as the ra/dec in dataDict.
750 self.assertTrue(np.all(np.equal(
751 val.values,
752 [14924528684992, 14924528689697, 14924528501716, 14924526434259,
753 14924526433879])))
755 def testEbv(self):
756 """Test that EBV works.
757 """
758 df = self.getMultiIndexDataFrame(self.dataDict)
759 func = Ebv()
761 val = self._funcVal(func, df)
762 np.testing.assert_array_almost_equal(
763 val.values,
764 [0.029100, 0.029013, 0.028857, 0.028802, 0.028797]
765 )
767 def _dropLevels(self, df):
768 levelsToDrop = [n for lev, n in zip(df.columns.levels, df.columns.names) if len(lev) == 1]
770 # Prevent error when trying to drop *all* columns
771 if len(levelsToDrop) == len(df.columns.names):
772 levelsToDrop.remove(df.columns.names[-1])
774 df.columns = df.columns.droplevel(levelsToDrop)
777class MyMemoryTestCase(lsst.utils.tests.MemoryTestCase):
778 pass
781def setup_module(module):
782 lsst.utils.tests.init()
785if __name__ == "__main__": 785 ↛ 786line 785 didn't jump to line 786, because the condition on line 785 was never true
786 lsst.utils.tests.init()
787 unittest.main()