Coverage for tests/test_functors.py: 12%
388 statements
« prev ^ index » next coverage.py v7.2.3, created at 2023-04-21 02:15 -0700
« prev ^ index » next coverage.py v7.2.3, created at 2023-04-21 02:15 -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
30import lsst.daf.base as dafBase
31import lsst.afw.geom as afwGeom
32import lsst.geom as geom
33from lsst.sphgeom import HtmPixelization
34import lsst.meas.base as measBase
35import lsst.utils.tests
36from lsst.pipe.base import InMemoryDatasetHandle
37from lsst.pipe.tasks.functors import (CompositeFunctor, CustomFunctor, Column, RAColumn,
38 DecColumn, Mag, MagDiff, Color, StarGalaxyLabeller,
39 DeconvolvedMoments, SdssTraceSize, PsfSdssTraceSizeDiff,
40 HsmTraceSize, PsfHsmTraceSizeDiff, HsmFwhm,
41 LocalPhotometry, LocalNanojansky, LocalNanojanskyErr,
42 LocalMagnitude, LocalMagnitudeErr,
43 LocalDipoleMeanFlux, LocalDipoleMeanFluxErr,
44 LocalDipoleDiffFlux, LocalDipoleDiffFluxErr,
45 LocalWcs, ComputePixelScale, ConvertPixelToArcseconds,
46 ConvertPixelSqToArcsecondsSq, Ratio, HtmIndex20)
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 testLabeller(self):
265 # Covering the code is better than nothing
266 self.columns.append("base_ClassificationExtendedness_value")
267 self.dataDict["base_ClassificationExtendedness_value"] = np.full(self.nRecords, 1)
268 df = self.getMultiIndexDataFrame(self.dataDict)
269 _ = self._funcVal(StarGalaxyLabeller(), df)
271 def testPixelScale(self):
272 # Test that the pixel scale and pix->arcsec calculations perform as
273 # expected.
274 pass
276 def testOther(self):
277 self.columns.extend(["ext_shapeHSM_HsmSourceMoments_xx", "ext_shapeHSM_HsmSourceMoments_yy",
278 "base_SdssShape_xx", "base_SdssShape_yy",
279 "ext_shapeHSM_HsmPsfMoments_xx", "ext_shapeHSM_HsmPsfMoments_yy",
280 "base_SdssShape_psf_xx", "base_SdssShape_psf_yy"])
281 self.dataDict["ext_shapeHSM_HsmSourceMoments_xx"] = np.full(self.nRecords, 1 / np.sqrt(2))
282 self.dataDict["ext_shapeHSM_HsmSourceMoments_yy"] = np.full(self.nRecords, 1 / np.sqrt(2))
283 self.dataDict["base_SdssShape_xx"] = np.full(self.nRecords, 1 / np.sqrt(2))
284 self.dataDict["base_SdssShape_yy"] = np.full(self.nRecords, 1 / np.sqrt(2))
285 self.dataDict["ext_shapeHSM_HsmPsfMoments_xx"] = np.full(self.nRecords, 1 / np.sqrt(2))
286 self.dataDict["ext_shapeHSM_HsmPsfMoments_yy"] = np.full(self.nRecords, 1 / np.sqrt(2))
287 self.dataDict["base_SdssShape_psf_xx"] = np.full(self.nRecords, 1)
288 self.dataDict["base_SdssShape_psf_yy"] = np.full(self.nRecords, 1)
289 df = self.getMultiIndexDataFrame(self.dataDict)
290 # Covering the code is better than nothing
291 for filt in self.bands:
292 for Func in [DeconvolvedMoments,
293 SdssTraceSize,
294 PsfSdssTraceSizeDiff,
295 HsmTraceSize, PsfHsmTraceSizeDiff, HsmFwhm]:
296 _ = self._funcVal(Func(filt=filt), df)
298 def _compositeFuncVal(self, functor, df):
299 self.assertIsInstance(functor, CompositeFunctor)
301 handle = self.getDatasetHandle(df)
303 fdf1 = functor(df)
304 fdf2 = functor(handle)
305 self.assertTrue(fdf1.equals(fdf2))
307 self.assertIsInstance(fdf1, pd.DataFrame)
308 self.assertTrue(np.all([k in fdf1.columns for k in functor.funcDict.keys()]))
310 fdf1 = functor(df, dropna=True)
311 fdf2 = functor(handle, dropna=True)
312 self.assertTrue(fdf1.equals(fdf2))
314 # Check that there are no nulls
315 self.assertFalse(fdf1.isnull().any(axis=None))
317 return fdf1
319 def _compositeDifferenceVal(self, functor, df1, df2):
320 self.assertIsInstance(functor, CompositeFunctor)
322 handle1 = self.getDatasetHandle(df1)
323 handle2 = self.getDatasetHandle(df2)
325 fdf1 = functor.difference(df1, df2)
326 fdf2 = functor.difference(handle1, handle2)
327 self.assertTrue(fdf1.equals(fdf2))
329 self.assertIsInstance(fdf1, pd.DataFrame)
330 self.assertTrue(np.all([k in fdf1.columns for k in functor.funcDict.keys()]))
332 fdf1 = functor.difference(df1, df2, dropna=True)
333 fdf2 = functor.difference(handle1, handle2, dropna=True)
334 self.assertTrue(fdf1.equals(fdf2))
336 # Check that there are no nulls
337 self.assertFalse(fdf1.isnull().any(axis=None))
339 df1_functored = functor(df1)
340 df2_functored = functor(df2)
342 self.assertTrue(np.allclose(fdf1.values, df1_functored.values - df2_functored.values))
344 return fdf1
346 def testComposite(self):
347 self.columns.extend(["modelfit_CModel_instFlux", "base_PsfFlux_instFlux"])
348 self.dataDict["modelfit_CModel_instFlux"] = np.full(self.nRecords, 1)
349 self.dataDict["base_PsfFlux_instFlux"] = np.full(self.nRecords, 1)
351 df = self.getMultiIndexDataFrame(self.dataDict)
352 # Modify r band value slightly.
353 df[("meas", "r", "base_PsfFlux_instFlux")] -= 0.1
355 filt = 'g'
356 funcDict = {'psfMag_ref': Mag('base_PsfFlux', dataset='ref'),
357 'ra': RAColumn(),
358 'dec': DecColumn(),
359 'psfMag': Mag('base_PsfFlux', filt=filt),
360 'cmodel_magDiff': MagDiff('base_PsfFlux',
361 'modelfit_CModel', filt=filt)}
362 func = CompositeFunctor(funcDict)
363 fdf1 = self._compositeFuncVal(func, df)
365 # Repeat same, but define filter globally instead of individually
366 funcDict2 = {'psfMag_ref': Mag('base_PsfFlux', dataset='ref'),
367 'ra': RAColumn(),
368 'dec': DecColumn(),
369 'psfMag': Mag('base_PsfFlux'),
370 'cmodel_magDiff': MagDiff('base_PsfFlux',
371 'modelfit_CModel')}
373 func2 = CompositeFunctor(funcDict2, filt=filt)
374 fdf2 = self._compositeFuncVal(func2, df)
375 self.assertTrue(fdf1.equals(fdf2))
377 func2.filt = 'r'
378 fdf3 = self._compositeFuncVal(func2, df)
379 # Because we modified the R filter this should fail.
380 self.assertFalse(fdf2.equals(fdf3))
382 # Make sure things work with passing list instead of dict
383 funcs = [Mag('base_PsfFlux', dataset='ref'),
384 RAColumn(),
385 DecColumn(),
386 Mag('base_PsfFlux', filt=filt),
387 MagDiff('base_PsfFlux', 'modelfit_CModel', filt=filt)]
389 _ = self._compositeFuncVal(CompositeFunctor(funcs), df)
391 def testCompositeSimple(self):
392 """Test single-level composite functor for functionality
393 """
394 self.columns.extend(["modelfit_CModel_instFlux", "base_PsfFlux_instFlux"])
395 self.dataDict["modelfit_CModel_instFlux"] = np.full(self.nRecords, 1)
396 self.dataDict["base_PsfFlux_instFlux"] = np.full(self.nRecords, 1)
398 df = self.getSimpleDataFrame(self.dataDict)
399 funcDict = {'ra': RAColumn(),
400 'dec': DecColumn(),
401 'psfMag': Mag('base_PsfFlux'),
402 'cmodel_magDiff': MagDiff('base_PsfFlux',
403 'modelfit_CModel')}
404 func = CompositeFunctor(funcDict)
405 _ = self._compositeFuncVal(func, df)
407 def testCompositeColor(self):
408 self.dataDict["base_PsfFlux_instFlux"] = np.full(self.nRecords, 1000)
409 self.dataDict["base_PsfFlux_instFluxErr"] = np.full(self.nRecords, 10)
410 df = self.getMultiIndexDataFrame(self.dataDict)
411 funcDict = {'a': Mag('base_PsfFlux', dataset='meas', filt='g'),
412 'b': Mag('base_PsfFlux', dataset='forced_src', filt='g'),
413 'c': Color('base_PsfFlux', 'g', 'r')}
414 # Covering the code is better than nothing
415 _ = self._compositeFuncVal(CompositeFunctor(funcDict), df)
417 def testCompositeDifference(self):
418 self.dataDict["base_PsfFlux_instFlux"] = np.full(self.nRecords, 1000)
419 self.dataDict["base_PsfFlux_instFluxErr"] = np.full(self.nRecords, 10)
420 df1 = self.getMultiIndexDataFrame(self.dataDict)
422 self.dataDict["base_PsfFlux_instFlux"] = np.full(self.nRecords, 999)
423 self.dataDict["base_PsfFlux_instFluxErr"] = np.full(self.nRecords, 9)
424 df2 = self.getMultiIndexDataFrame(self.dataDict)
426 funcDict = {'a': Mag('base_PsfFlux', dataset='meas', filt='g'),
427 'b': Mag('base_PsfFlux', dataset='forced_src', filt='g'),
428 'c': Color('base_PsfFlux', 'g', 'r')}
429 # Covering the code is better than nothing
430 _ = self._compositeDifferenceVal(CompositeFunctor(funcDict), df1, df2)
432 def testCompositeFail(self):
433 """Test a composite functor where one of the functors should be junk.
434 """
435 self.dataDict["base_PsfFlux_instFlux"] = np.full(self.nRecords, 1000)
436 df = self.getMultiIndexDataFrame(self.dataDict)
438 funcDict = {'good': Column("base_PsfFlux_instFlux"),
439 'bad': Column('not_a_column')}
441 _ = self._compositeFuncVal(CompositeFunctor(funcDict), df)
443 def testLocalPhotometry(self):
444 """Test the local photometry functors.
445 """
446 flux = 1000
447 fluxErr = 10
448 calib = 10
449 calibErr = 1
450 self.dataDict["base_PsfFlux_instFlux"] = np.full(self.nRecords, flux)
451 self.dataDict["base_PsfFlux_instFluxErr"] = np.full(self.nRecords,
452 fluxErr)
453 self.dataDict["base_LocalPhotoCalib"] = np.full(self.nRecords, calib)
454 self.dataDict["base_LocalPhotoCalibErr"] = np.full(self.nRecords,
455 calibErr)
456 df = self.getMultiIndexDataFrame(self.dataDict)
457 func = LocalPhotometry("base_PsfFlux_instFlux",
458 "base_PsfFlux_instFluxErr",
459 "base_LocalPhotoCalib",
460 "base_LocalPhotoCalibErr")
462 nanoJansky = func.instFluxToNanojansky(
463 df[("meas", "g", "base_PsfFlux_instFlux")],
464 df[("meas", "g", "base_LocalPhotoCalib")])
465 mag = func.instFluxToMagnitude(
466 df[("meas", "g", "base_PsfFlux_instFlux")],
467 df[("meas", "g", "base_LocalPhotoCalib")])
468 nanoJanskyErr = func.instFluxErrToNanojanskyErr(
469 df[("meas", "g", "base_PsfFlux_instFlux")],
470 df[("meas", "g", "base_PsfFlux_instFluxErr")],
471 df[("meas", "g", "base_LocalPhotoCalib")],
472 df[("meas", "g", "base_LocalPhotoCalibErr")])
473 magErr = func.instFluxErrToMagnitudeErr(
474 df[("meas", "g", "base_PsfFlux_instFlux")],
475 df[("meas", "g", "base_PsfFlux_instFluxErr")],
476 df[("meas", "g", "base_LocalPhotoCalib")],
477 df[("meas", "g", "base_LocalPhotoCalibErr")])
479 self.assertTrue(np.allclose(nanoJansky.values,
480 flux * calib,
481 atol=1e-13,
482 rtol=0))
483 self.assertTrue(np.allclose(mag.values,
484 (flux * calib * u.nJy).to_value(u.ABmag),
485 atol=1e-13,
486 rtol=0))
487 self.assertTrue(np.allclose(nanoJanskyErr.values,
488 np.hypot(fluxErr * calib, flux * calibErr),
489 atol=1e-13,
490 rtol=0))
491 self.assertTrue(np.allclose(
492 magErr.values,
493 2.5 / np.log(10) * nanoJanskyErr.values / nanoJansky.values,
494 atol=1e-13,
495 rtol=0))
497 # Test functors against the values computed above.
498 self._testLocalPhotometryFunctors(LocalNanojansky,
499 df,
500 nanoJansky)
501 self._testLocalPhotometryFunctors(LocalNanojanskyErr,
502 df,
503 nanoJanskyErr)
504 self._testLocalPhotometryFunctors(LocalMagnitude,
505 df,
506 mag)
507 self._testLocalPhotometryFunctors(LocalMagnitudeErr,
508 df,
509 magErr)
511 def _testLocalPhotometryFunctors(self, functor, df, testValues):
512 func = functor("base_PsfFlux_instFlux",
513 "base_PsfFlux_instFluxErr",
514 "base_LocalPhotoCalib",
515 "base_LocalPhotoCalibErr")
516 val = self._funcVal(func, df)
517 self.assertTrue(np.allclose(testValues.values,
518 val.values,
519 atol=1e-13,
520 rtol=0))
522 def testDipPhotometry(self):
523 """Test calibrated flux calculations for dipoles."""
524 fluxNeg = -100
525 fluxPos = 101
526 fluxErr = 10
527 calib = 10
528 calibErr = 1
530 # compute expected values.
531 absMean = 0.5*(fluxPos - fluxNeg)*calib
532 absDiff = (fluxNeg + fluxPos)*calib
533 absMeanErr = 0.5*np.sqrt(2*(fluxErr*calib)**2
534 + ((fluxPos - fluxNeg)*calibErr)**2)
535 absDiffErr = np.sqrt(2*(fluxErr*calib)**2
536 + ((fluxPos + fluxNeg)*calibErr)**2)
538 self.dataDict["ip_diffim_DipoleFluxNeg_instFlux"] = np.full(self.nRecords, fluxNeg)
539 self.dataDict["ip_diffim_DipoleFluxNeg_instFluxErr"] = np.full(self.nRecords, fluxErr)
540 self.dataDict["ip_diffim_DipoleFluxPos_instFlux"] = np.full(self.nRecords, fluxPos)
541 self.dataDict["ip_diffim_DipoleFluxPos_instFluxErr"] = np.full(self.nRecords, fluxErr)
542 self.dataDict["base_LocalPhotoCalib"] = np.full(self.nRecords, calib)
543 self.dataDict["base_LocalPhotoCalibErr"] = np.full(self.nRecords,
544 calibErr)
546 df = self.getMultiIndexDataFrame(self.dataDict)
547 func = LocalDipoleMeanFlux("ip_diffim_DipoleFluxPos_instFlux",
548 "ip_diffim_DipoleFluxNeg_instFlux",
549 "ip_diffim_DipoleFluxPos_instFluxErr",
550 "ip_diffim_DipoleFluxNeg_instFluxErr",
551 "base_LocalPhotoCalib",
552 "base_LocalPhotoCalibErr")
553 val = self._funcVal(func, df)
554 self.assertTrue(np.allclose(val.values,
555 absMean,
556 atol=1e-13,
557 rtol=0))
559 func = LocalDipoleMeanFluxErr("ip_diffim_DipoleFluxPos_instFlux",
560 "ip_diffim_DipoleFluxNeg_instFlux",
561 "ip_diffim_DipoleFluxPos_instFluxErr",
562 "ip_diffim_DipoleFluxNeg_instFluxErr",
563 "base_LocalPhotoCalib",
564 "base_LocalPhotoCalibErr")
565 val = self._funcVal(func, df)
566 self.assertTrue(np.allclose(val.values,
567 absMeanErr,
568 atol=1e-13,
569 rtol=0))
571 func = LocalDipoleDiffFlux("ip_diffim_DipoleFluxPos_instFlux",
572 "ip_diffim_DipoleFluxNeg_instFlux",
573 "ip_diffim_DipoleFluxPos_instFluxErr",
574 "ip_diffim_DipoleFluxNeg_instFluxErr",
575 "base_LocalPhotoCalib",
576 "base_LocalPhotoCalibErr")
577 val = self._funcVal(func, df)
578 self.assertTrue(np.allclose(val.values,
579 absDiff,
580 atol=1e-13,
581 rtol=0))
583 func = LocalDipoleDiffFluxErr("ip_diffim_DipoleFluxPos_instFlux",
584 "ip_diffim_DipoleFluxNeg_instFlux",
585 "ip_diffim_DipoleFluxPos_instFluxErr",
586 "ip_diffim_DipoleFluxNeg_instFluxErr",
587 "base_LocalPhotoCalib",
588 "base_LocalPhotoCalibErr")
589 val = self._funcVal(func, df)
590 self.assertTrue(np.allclose(val.values,
591 absDiffErr,
592 atol=1e-13,
593 rtol=0))
595 def testConvertPixelToArcseconds(self):
596 """Test calculations of the pixel scale and conversions of pixel to
597 arcseconds.
598 """
599 dipoleSep = 10
600 ixx = 10
601 testPixelDeltas = np.random.uniform(-100, 100, size=(self.nRecords, 2))
602 import lsst.afw.table as afwTable
603 localWcsPlugin = measBase.EvaluateLocalWcsPlugin(
604 None,
605 "base_LocalWcs",
606 afwTable.SourceTable.makeMinimalSchema(),
607 None)
608 for dec in np.linspace(-90, 90, 10):
609 for x, y in zip(np.random.uniform(2 * 1109.99981456774, size=10),
610 np.random.uniform(2 * 560.018167811613, size=10)):
611 center = geom.Point2D(x, y)
612 wcs = self._makeWcs(dec)
613 skyOrigin = wcs.pixelToSky(center)
615 linAffMatrix = localWcsPlugin.makeLocalTransformMatrix(wcs,
616 center)
617 self.dataDict["dipoleSep"] = np.full(self.nRecords, dipoleSep)
618 self.dataDict["ixx"] = np.full(self.nRecords, ixx)
619 self.dataDict["slot_Centroid_x"] = np.full(self.nRecords, x)
620 self.dataDict["slot_Centroid_y"] = np.full(self.nRecords, y)
621 self.dataDict["someCentroid_x"] = x + testPixelDeltas[:, 0]
622 self.dataDict["someCentroid_y"] = y + testPixelDeltas[:, 1]
623 self.dataDict["base_LocalWcs_CDMatrix_1_1"] = np.full(self.nRecords,
624 linAffMatrix[0, 0])
625 self.dataDict["base_LocalWcs_CDMatrix_1_2"] = np.full(self.nRecords,
626 linAffMatrix[0, 1])
627 self.dataDict["base_LocalWcs_CDMatrix_2_1"] = np.full(self.nRecords,
628 linAffMatrix[1, 0])
629 self.dataDict["base_LocalWcs_CDMatrix_2_2"] = np.full(self.nRecords,
630 linAffMatrix[1, 1])
631 df = self.getMultiIndexDataFrame(self.dataDict)
632 func = LocalWcs("base_LocalWcs_CDMatrix_1_1",
633 "base_LocalWcs_CDMatrix_1_2",
634 "base_LocalWcs_CDMatrix_2_1",
635 "base_LocalWcs_CDMatrix_2_2")
637 # Exercise the full set of functions in LocalWcs.
638 sepRadians = func.getSkySeperationFromPixel(
639 df[("meas", "g", "someCentroid_x")] - df[("meas", "g", "slot_Centroid_x")],
640 df[("meas", "g", "someCentroid_y")] - df[("meas", "g", "slot_Centroid_y")],
641 0.0,
642 0.0,
643 df[("meas", "g", "base_LocalWcs_CDMatrix_1_1")],
644 df[("meas", "g", "base_LocalWcs_CDMatrix_1_2")],
645 df[("meas", "g", "base_LocalWcs_CDMatrix_2_1")],
646 df[("meas", "g", "base_LocalWcs_CDMatrix_2_2")])
648 # Test functor values against afw SkyWcs computations.
649 for centX, centY, sep in zip(testPixelDeltas[:, 0],
650 testPixelDeltas[:, 1],
651 sepRadians.values):
652 afwSepRadians = skyOrigin.separation(
653 wcs.pixelToSky(x + centX, y + centY)).asRadians()
654 self.assertAlmostEqual(1 - sep / afwSepRadians, 0, places=6)
656 # Test the pixel scale computation.
657 func = ComputePixelScale("base_LocalWcs_CDMatrix_1_1",
658 "base_LocalWcs_CDMatrix_1_2",
659 "base_LocalWcs_CDMatrix_2_1",
660 "base_LocalWcs_CDMatrix_2_2")
661 pixelScale = self._funcVal(func, df)
662 self.assertTrue(np.allclose(
663 wcs.getPixelScale(center).asArcseconds(),
664 pixelScale.values,
665 rtol=1e-8,
666 atol=0))
668 # Test pixel -> arcsec conversion.
669 func = ConvertPixelToArcseconds("dipoleSep",
670 "base_LocalWcs_CDMatrix_1_1",
671 "base_LocalWcs_CDMatrix_1_2",
672 "base_LocalWcs_CDMatrix_2_1",
673 "base_LocalWcs_CDMatrix_2_2")
674 val = self._funcVal(func, df)
675 self.assertTrue(np.allclose(pixelScale.values * dipoleSep,
676 val.values,
677 atol=1e-16,
678 rtol=1e-16))
680 # Test pixel^2 -> arcsec^2 conversion.
681 func = ConvertPixelSqToArcsecondsSq("ixx",
682 "base_LocalWcs_CDMatrix_1_1",
683 "base_LocalWcs_CDMatrix_1_2",
684 "base_LocalWcs_CDMatrix_2_1",
685 "base_LocalWcs_CDMatrix_2_2")
686 val = self._funcVal(func, df)
687 self.assertTrue(np.allclose(pixelScale.values ** 2 * dipoleSep,
688 val.values,
689 atol=1e-16,
690 rtol=1e-16))
692 def _makeWcs(self, dec=53.1595451514076):
693 """Create a wcs from real CFHT values.
695 Returns
696 -------
697 wcs : `lsst.afw.geom`
698 Created wcs.
699 """
700 metadata = dafBase.PropertySet()
702 metadata.set("SIMPLE", "T")
703 metadata.set("BITPIX", -32)
704 metadata.set("NAXIS", 2)
705 metadata.set("NAXIS1", 1024)
706 metadata.set("NAXIS2", 1153)
707 metadata.set("RADECSYS", 'FK5')
708 metadata.set("EQUINOX", 2000.)
710 metadata.setDouble("CRVAL1", 215.604025685476)
711 metadata.setDouble("CRVAL2", dec)
712 metadata.setDouble("CRPIX1", 1109.99981456774)
713 metadata.setDouble("CRPIX2", 560.018167811613)
714 metadata.set("CTYPE1", 'RA---SIN')
715 metadata.set("CTYPE2", 'DEC--SIN')
717 metadata.setDouble("CD1_1", 5.10808596133527E-05)
718 metadata.setDouble("CD1_2", 1.85579539217196E-07)
719 metadata.setDouble("CD2_2", -5.10281493481982E-05)
720 metadata.setDouble("CD2_1", -8.27440751733828E-07)
722 return afwGeom.makeSkyWcs(metadata)
724 def testRatio(self):
725 """Test the ratio functor where one of the functors should be junk.
726 """
727 self.dataDict["base_PsfFlux_instFlux"] = np.full(self.nRecords, 1000)
728 self.dataDict["base_PsfFlux_instFluxErr"] = np.full(self.nRecords, 100)
729 df = self.getMultiIndexDataFrame(self.dataDict)
731 func = Ratio("base_PsfFlux_instFlux", "base_PsfFlux_instFluxErr")
733 val = self._funcVal(func, df)
734 self.assertTrue(np.allclose(np.full(self.nRecords, 10),
735 val.values,
736 atol=1e-16,
737 rtol=1e-16))
739 def testHtm(self):
740 """Test that HtmIndxes are created as expected.
741 """
742 df = self.getMultiIndexDataFrame(self.dataDict)
743 func = HtmIndex20("coord_ra", "coord_dec")
745 val = self._funcVal(func, df)
746 # Test that the HtmIds come out as the ra/dec in dataDict.
747 self.assertTrue(np.all(np.equal(
748 val.values,
749 [14924528684992, 14924528689697, 14924528501716, 14924526434259,
750 14924526433879])))
752 def _dropLevels(self, df):
753 levelsToDrop = [n for lev, n in zip(df.columns.levels, df.columns.names) if len(lev) == 1]
755 # Prevent error when trying to drop *all* columns
756 if len(levelsToDrop) == len(df.columns.names):
757 levelsToDrop.remove(df.columns.names[-1])
759 df.columns = df.columns.droplevel(levelsToDrop)
762class MyMemoryTestCase(lsst.utils.tests.MemoryTestCase):
763 pass
766def setup_module(module):
767 lsst.utils.tests.init()
770if __name__ == "__main__": 770 ↛ 771line 770 didn't jump to line 771, because the condition on line 770 was never true
771 lsst.utils.tests.init()
772 unittest.main()