Coverage for tests/test_functors.py: 13%
383 statements
« prev ^ index » next coverage.py v6.5.0, created at 2023-04-15 10:49 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2023-04-15 10:49 +0000
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.parquetTable import MultilevelParquetTable, ParquetTable
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)
49ROOT = os.path.abspath(os.path.dirname(__file__))
52class FunctorTestCase(unittest.TestCase):
54 def simulateMultiParquet(self, dataDict):
55 """Create a simple test MultilevelParquetTable
56 """
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 MultilevelParquetTable(dataFrame=df)
74 def simulateParquet(self, dataDict):
75 df = pd.DataFrame(dataDict)
76 return ParquetTable(dataFrame=df)
78 def getDatasetHandle(self, parq):
79 df = parq._df
80 lo, hi = HtmPixelization(7).universe().ranges()[0]
81 value = np.random.randint(lo, hi)
82 return InMemoryDatasetHandle(df, storageClass="DataFrame", dataId={"htm7": value})
84 def setUp(self):
85 np.random.seed(12345)
86 self.datasets = ['forced_src', 'meas', 'ref']
87 self.bands = ['g', 'r']
88 self.columns = ['coord_ra', 'coord_dec']
89 self.nRecords = 5
90 self.dataDict = {
91 "coord_ra": [3.77654137, 3.77643059, 3.77621148, 3.77611944, 3.77610396],
92 "coord_dec": [0.01127624, 0.01127787, 0.01127543, 0.01127543, 0.01127543]}
94 def _funcVal(self, functor, parq):
95 self.assertIsInstance(functor.name, str)
96 self.assertIsInstance(functor.shortname, str)
98 handle = self.getDatasetHandle(parq)
100 val = functor(parq)
101 val2 = functor(handle)
102 self.assertTrue((val == val2).all())
103 self.assertIsInstance(val, pd.Series)
105 val = functor(parq, dropna=True)
106 val2 = functor(handle, dropna=True)
107 self.assertTrue((val == val2).all())
108 self.assertEqual(val.isnull().sum(), 0)
110 return val
112 def _differenceVal(self, functor, parq1, parq2):
113 self.assertIsInstance(functor.name, str)
114 self.assertIsInstance(functor.shortname, str)
116 handle1 = self.getDatasetHandle(parq1)
117 handle2 = self.getDatasetHandle(parq2)
119 val = functor.difference(parq1, parq2)
120 val2 = functor.difference(handle1, handle2)
121 self.assertTrue(val.equals(val2))
122 self.assertIsInstance(val, pd.Series)
124 val = functor.difference(parq1, parq2, dropna=True)
125 val2 = functor.difference(handle1, handle2, dropna=True)
126 self.assertTrue(val.equals(val2))
127 self.assertEqual(val.isnull().sum(), 0)
129 val1 = self._funcVal(functor, parq1)
130 val2 = self._funcVal(functor, parq2)
132 self.assertTrue(np.allclose(val, val1 - val2))
134 return val
136 def testColumn(self):
137 self.columns.append("base_FootprintArea_value")
138 self.dataDict["base_FootprintArea_value"] = \
139 np.full(self.nRecords, 1)
140 parq = self.simulateMultiParquet(self.dataDict)
141 func = Column('base_FootprintArea_value', filt='g')
142 self._funcVal(func, parq)
144 parq = self.simulateParquet(self.dataDict)
145 func = Column('base_FootprintArea_value')
146 self._funcVal(func, parq)
148 def testCustom(self):
149 self.columns.append("base_FootprintArea_value")
150 self.dataDict["base_FootprintArea_value"] = \
151 np.random.rand(self.nRecords)
152 parq = self.simulateMultiParquet(self.dataDict)
153 func = CustomFunctor('2*base_FootprintArea_value', filt='g')
154 val = self._funcVal(func, parq)
156 func2 = Column('base_FootprintArea_value', filt='g')
158 np.allclose(val.values, 2*func2(parq).values, atol=1e-13, rtol=0)
160 parq = self.simulateParquet(self.dataDict)
161 func = CustomFunctor('2 * base_FootprintArea_value')
162 val = self._funcVal(func, parq)
163 func2 = Column('base_FootprintArea_value')
165 np.allclose(val.values, 2*func2(parq).values, atol=1e-13, rtol=0)
167 def testCoords(self):
168 parq = self.simulateMultiParquet(self.dataDict)
169 ra = self._funcVal(RAColumn(), parq)
170 dec = self._funcVal(DecColumn(), parq)
172 columnDict = {'dataset': 'ref', 'band': 'g',
173 'column': ['coord_ra', 'coord_dec']}
175 coords = parq.toDataFrame(columns=columnDict, droplevels=True) / np.pi * 180.
177 self.assertTrue(np.allclose(ra, coords[('ref', 'g', 'coord_ra')], atol=1e-13, rtol=0))
178 self.assertTrue(np.allclose(dec, coords[('ref', 'g', 'coord_dec')], atol=1e-13, rtol=0))
180 # single-level column index table
181 parq = self.simulateParquet(self.dataDict)
182 ra = self._funcVal(RAColumn(), parq)
183 dec = self._funcVal(DecColumn(), parq)
185 coords = parq.toDataFrame(columns=['coord_ra', 'coord_dec']) / np.pi * 180.
187 self.assertTrue(np.allclose(ra, coords['coord_ra'], atol=1e-13, rtol=0))
188 self.assertTrue(np.allclose(dec, coords['coord_dec'], atol=1e-13, rtol=0))
190 def testMag(self):
191 self.columns.extend(["base_PsfFlux_instFlux", "base_PsfFlux_instFluxErr"])
192 self.dataDict["base_PsfFlux_instFlux"] = np.full(self.nRecords, 1000)
193 self.dataDict["base_PsfFlux_instFluxErr"] = np.full(self.nRecords, 10)
194 parq = self.simulateMultiParquet(self.dataDict)
195 # Change one dataset filter combinations value.
196 parq._df[("meas", "g", "base_PsfFlux_instFlux")] -= 1
198 fluxName = 'base_PsfFlux'
200 # Check that things work when you provide dataset explicitly
201 for dataset in ['forced_src', 'meas']:
202 psfMag_G = self._funcVal(Mag(fluxName, dataset=dataset,
203 filt='g'),
204 parq)
205 psfMag_R = self._funcVal(Mag(fluxName, dataset=dataset,
206 filt='r'),
207 parq)
209 psfColor_GR = self._funcVal(Color(fluxName, 'g', 'r',
210 dataset=dataset),
211 parq)
213 self.assertTrue(np.allclose((psfMag_G - psfMag_R).dropna(), psfColor_GR, rtol=0, atol=1e-13))
215 # Check that behavior as expected when dataset not provided;
216 # that is, that the color comes from forced and default Mag is meas
217 psfMag_G = self._funcVal(Mag(fluxName, filt='g'), parq)
218 psfMag_R = self._funcVal(Mag(fluxName, filt='r'), parq)
220 psfColor_GR = self._funcVal(Color(fluxName, 'g', 'r'), parq)
222 # These should *not* be equal.
223 self.assertFalse(np.allclose((psfMag_G - psfMag_R).dropna(), psfColor_GR))
225 def testMagDiff(self):
226 self.columns.extend(["base_PsfFlux_instFlux", "base_PsfFlux_instFluxErr",
227 "modelfit_CModel_instFlux", "modelfit_CModel_instFluxErr"])
228 self.dataDict["base_PsfFlux_instFlux"] = np.full(self.nRecords, 1000)
229 self.dataDict["base_PsfFlux_instFluxErr"] = np.full(self.nRecords, 10)
230 self.dataDict["modelfit_CModel_instFlux"] = np.full(self.nRecords, 1000)
231 self.dataDict["modelfit_CModel_instFluxErr"] = np.full(self.nRecords, 10)
232 parq = self.simulateMultiParquet(self.dataDict)
234 for filt in self.bands:
235 filt = 'g'
236 val = self._funcVal(MagDiff('base_PsfFlux', 'modelfit_CModel', filt=filt), parq)
238 mag1 = self._funcVal(Mag('modelfit_CModel', filt=filt), parq)
239 mag2 = self._funcVal(Mag('base_PsfFlux', filt=filt), parq)
240 self.assertTrue(np.allclose((mag2 - mag1).dropna(), val, rtol=0, atol=1e-13))
242 def testDifference(self):
243 """Test .difference method using MagDiff as the example.
244 """
245 self.columns.extend(["base_PsfFlux_instFlux", "base_PsfFlux_instFluxErr",
246 "modelfit_CModel_instFlux", "modelfit_CModel_instFluxErr"])
248 self.dataDict["base_PsfFlux_instFlux"] = np.full(self.nRecords, 1000)
249 self.dataDict["modelfit_CModel_instFlux"] = np.full(self.nRecords, 1000)
250 parq1 = self.simulateMultiParquet(self.dataDict)
252 self.dataDict["base_PsfFlux_instFlux"] = np.full(self.nRecords, 999)
253 self.dataDict["modelfit_CModel_instFlux"] = np.full(self.nRecords, 999)
254 parq2 = self.simulateMultiParquet(self.dataDict)
256 magDiff = MagDiff('base_PsfFlux', 'modelfit_CModel', filt='g')
258 # Asserts that differences computed properly
259 self._differenceVal(magDiff, parq1, parq2)
261 def testLabeller(self):
262 # Covering the code is better than nothing
263 self.columns.append("base_ClassificationExtendedness_value")
264 self.dataDict["base_ClassificationExtendedness_value"] = np.full(self.nRecords, 1)
265 parq = self.simulateMultiParquet(self.dataDict)
266 labels = self._funcVal(StarGalaxyLabeller(), parq) # noqa
268 def testPixelScale(self):
269 # Test that the pixel scale and pix->arcsec calculations perform as
270 # expected.
271 pass
273 def testOther(self):
274 self.columns.extend(["ext_shapeHSM_HsmSourceMoments_xx", "ext_shapeHSM_HsmSourceMoments_yy",
275 "base_SdssShape_xx", "base_SdssShape_yy",
276 "ext_shapeHSM_HsmPsfMoments_xx", "ext_shapeHSM_HsmPsfMoments_yy",
277 "base_SdssShape_psf_xx", "base_SdssShape_psf_yy"])
278 self.dataDict["ext_shapeHSM_HsmSourceMoments_xx"] = np.full(self.nRecords, 1 / np.sqrt(2))
279 self.dataDict["ext_shapeHSM_HsmSourceMoments_yy"] = np.full(self.nRecords, 1 / np.sqrt(2))
280 self.dataDict["base_SdssShape_xx"] = np.full(self.nRecords, 1 / np.sqrt(2))
281 self.dataDict["base_SdssShape_yy"] = np.full(self.nRecords, 1 / np.sqrt(2))
282 self.dataDict["ext_shapeHSM_HsmPsfMoments_xx"] = np.full(self.nRecords, 1 / np.sqrt(2))
283 self.dataDict["ext_shapeHSM_HsmPsfMoments_yy"] = np.full(self.nRecords, 1 / np.sqrt(2))
284 self.dataDict["base_SdssShape_psf_xx"] = np.full(self.nRecords, 1)
285 self.dataDict["base_SdssShape_psf_yy"] = np.full(self.nRecords, 1)
286 parq = self.simulateMultiParquet(self.dataDict)
287 # Covering the code is better than nothing
288 for filt in self.bands:
289 for Func in [DeconvolvedMoments,
290 SdssTraceSize,
291 PsfSdssTraceSizeDiff,
292 HsmTraceSize, PsfHsmTraceSizeDiff, HsmFwhm]:
293 val = self._funcVal(Func(filt=filt), parq) # noqa
295 def _compositeFuncVal(self, functor, parq):
296 self.assertIsInstance(functor, CompositeFunctor)
298 handle = self.getDatasetHandle(parq)
300 df = functor(parq)
301 df2 = functor(handle)
302 self.assertTrue(df.equals(df2))
304 self.assertIsInstance(df, pd.DataFrame)
305 self.assertTrue(np.all([k in df.columns for k in functor.funcDict.keys()]))
307 df = functor(parq, dropna=True)
308 df2 = functor(handle, dropna=True)
309 self.assertTrue(df.equals(df2))
311 # Check that there are no nulls
312 self.assertFalse(df.isnull().any(axis=None))
314 return df
316 def _compositeDifferenceVal(self, functor, parq1, parq2):
317 self.assertIsInstance(functor, CompositeFunctor)
319 handle1 = self.getDatasetHandle(parq1)
320 handle2 = self.getDatasetHandle(parq2)
322 df = functor.difference(parq1, parq2)
323 df2 = functor.difference(handle1, handle2)
324 self.assertTrue(df.equals(df2))
326 self.assertIsInstance(df, pd.DataFrame)
327 self.assertTrue(np.all([k in df.columns for k in functor.funcDict.keys()]))
329 df = functor.difference(parq1, parq2, dropna=True)
330 df2 = functor.difference(handle1, handle2, dropna=True)
331 self.assertTrue(df.equals(df2))
333 # Check that there are no nulls
334 self.assertFalse(df.isnull().any(axis=None))
336 df1 = functor(parq1)
337 df2 = functor(parq2)
339 self.assertTrue(np.allclose(df.values, df1.values - df2.values))
341 return df
343 def testComposite(self):
344 self.columns.extend(["modelfit_CModel_instFlux", "base_PsfFlux_instFlux"])
345 self.dataDict["modelfit_CModel_instFlux"] = np.full(self.nRecords, 1)
346 self.dataDict["base_PsfFlux_instFlux"] = np.full(self.nRecords, 1)
348 parq = self.simulateMultiParquet(self.dataDict)
349 # Modify r band value slightly.
350 parq._df[("meas", "r", "base_PsfFlux_instFlux")] -= 0.1
352 filt = 'g'
353 funcDict = {'psfMag_ref': Mag('base_PsfFlux', dataset='ref'),
354 'ra': RAColumn(),
355 'dec': DecColumn(),
356 'psfMag': Mag('base_PsfFlux', filt=filt),
357 'cmodel_magDiff': MagDiff('base_PsfFlux',
358 'modelfit_CModel', filt=filt)}
359 func = CompositeFunctor(funcDict)
360 df = self._compositeFuncVal(func, parq)
362 # Repeat same, but define filter globally instead of individually
363 funcDict2 = {'psfMag_ref': Mag('base_PsfFlux', dataset='ref'),
364 'ra': RAColumn(),
365 'dec': DecColumn(),
366 'psfMag': Mag('base_PsfFlux'),
367 'cmodel_magDiff': MagDiff('base_PsfFlux',
368 'modelfit_CModel')}
370 func2 = CompositeFunctor(funcDict2, filt=filt)
371 df2 = self._compositeFuncVal(func2, parq)
372 self.assertTrue(df.equals(df2))
374 func2.filt = 'r'
375 df3 = self._compositeFuncVal(func2, parq)
376 # Because we modified the R filter this should fail.
377 self.assertFalse(df2.equals(df3))
379 # Make sure things work with passing list instead of dict
380 funcs = [Mag('base_PsfFlux', dataset='ref'),
381 RAColumn(),
382 DecColumn(),
383 Mag('base_PsfFlux', filt=filt),
384 MagDiff('base_PsfFlux', 'modelfit_CModel', filt=filt)]
386 df = self._compositeFuncVal(CompositeFunctor(funcs), parq)
388 def testCompositeSimple(self):
389 """Test single-level composite functor for functionality
390 """
391 self.columns.extend(["modelfit_CModel_instFlux", "base_PsfFlux_instFlux"])
392 self.dataDict["modelfit_CModel_instFlux"] = np.full(self.nRecords, 1)
393 self.dataDict["base_PsfFlux_instFlux"] = np.full(self.nRecords, 1)
395 parq = self.simulateParquet(self.dataDict)
396 funcDict = {'ra': RAColumn(),
397 'dec': DecColumn(),
398 'psfMag': Mag('base_PsfFlux'),
399 'cmodel_magDiff': MagDiff('base_PsfFlux',
400 'modelfit_CModel')}
401 func = CompositeFunctor(funcDict)
402 df = self._compositeFuncVal(func, parq) # noqa
404 def testCompositeColor(self):
405 self.dataDict["base_PsfFlux_instFlux"] = np.full(self.nRecords, 1000)
406 self.dataDict["base_PsfFlux_instFluxErr"] = np.full(self.nRecords, 10)
407 parq = self.simulateMultiParquet(self.dataDict)
408 funcDict = {'a': Mag('base_PsfFlux', dataset='meas', filt='g'),
409 'b': Mag('base_PsfFlux', dataset='forced_src', filt='g'),
410 'c': Color('base_PsfFlux', 'g', 'r')}
411 # Covering the code is better than nothing
412 df = self._compositeFuncVal(CompositeFunctor(funcDict), parq) # noqa
414 def testCompositeDifference(self):
415 self.dataDict["base_PsfFlux_instFlux"] = np.full(self.nRecords, 1000)
416 self.dataDict["base_PsfFlux_instFluxErr"] = np.full(self.nRecords, 10)
417 parq1 = self.simulateMultiParquet(self.dataDict)
419 self.dataDict["base_PsfFlux_instFlux"] = np.full(self.nRecords, 999)
420 self.dataDict["base_PsfFlux_instFluxErr"] = np.full(self.nRecords, 9)
421 parq2 = self.simulateMultiParquet(self.dataDict)
423 funcDict = {'a': Mag('base_PsfFlux', dataset='meas', filt='g'),
424 'b': Mag('base_PsfFlux', dataset='forced_src', filt='g'),
425 'c': Color('base_PsfFlux', 'g', 'r')}
426 # Covering the code is better than nothing
427 df = self._compositeDifferenceVal(CompositeFunctor(funcDict), parq1, parq2) # noqa
429 def testCompositeFail(self):
430 """Test a composite functor where one of the functors should be junk.
431 """
432 self.dataDict["base_PsfFlux_instFlux"] = np.full(self.nRecords, 1000)
433 parq = self.simulateMultiParquet(self.dataDict)
435 funcDict = {'good': Column("base_PsfFlux_instFlux"),
436 'bad': Column('not_a_column')}
438 df = self._compositeFuncVal(CompositeFunctor(funcDict), parq) # noqa
440 def testLocalPhotometry(self):
441 """Test the local photometry functors.
442 """
443 flux = 1000
444 fluxErr = 10
445 calib = 10
446 calibErr = 1
447 self.dataDict["base_PsfFlux_instFlux"] = np.full(self.nRecords, flux)
448 self.dataDict["base_PsfFlux_instFluxErr"] = np.full(self.nRecords,
449 fluxErr)
450 self.dataDict["base_LocalPhotoCalib"] = np.full(self.nRecords, calib)
451 self.dataDict["base_LocalPhotoCalibErr"] = np.full(self.nRecords,
452 calibErr)
453 parq = self.simulateMultiParquet(self.dataDict)
454 func = LocalPhotometry("base_PsfFlux_instFlux",
455 "base_PsfFlux_instFluxErr",
456 "base_LocalPhotoCalib",
457 "base_LocalPhotoCalibErr")
458 df = parq.toDataFrame(columns={"dataset": "meas",
459 "band": "g",
460 "columns": ["base_PsfFlux_instFlux",
461 "base_PsfFlux_instFluxErr",
462 "base_LocalPhotoCalib",
463 "base_LocalPhotoCalibErr"]})
464 nanoJansky = func.instFluxToNanojansky(
465 df[("meas", "g", "base_PsfFlux_instFlux")],
466 df[("meas", "g", "base_LocalPhotoCalib")])
467 mag = func.instFluxToMagnitude(
468 df[("meas", "g", "base_PsfFlux_instFlux")],
469 df[("meas", "g", "base_LocalPhotoCalib")])
470 nanoJanskyErr = func.instFluxErrToNanojanskyErr(
471 df[("meas", "g", "base_PsfFlux_instFlux")],
472 df[("meas", "g", "base_PsfFlux_instFluxErr")],
473 df[("meas", "g", "base_LocalPhotoCalib")],
474 df[("meas", "g", "base_LocalPhotoCalibErr")])
475 magErr = func.instFluxErrToMagnitudeErr(
476 df[("meas", "g", "base_PsfFlux_instFlux")],
477 df[("meas", "g", "base_PsfFlux_instFluxErr")],
478 df[("meas", "g", "base_LocalPhotoCalib")],
479 df[("meas", "g", "base_LocalPhotoCalibErr")])
481 self.assertTrue(np.allclose(nanoJansky.values,
482 flux * calib,
483 atol=1e-13,
484 rtol=0))
485 self.assertTrue(np.allclose(mag.values,
486 (flux * calib * u.nJy).to_value(u.ABmag),
487 atol=1e-13,
488 rtol=0))
489 self.assertTrue(np.allclose(nanoJanskyErr.values,
490 np.hypot(fluxErr * calib, flux * calibErr),
491 atol=1e-13,
492 rtol=0))
493 self.assertTrue(np.allclose(
494 magErr.values,
495 2.5 / np.log(10) * nanoJanskyErr.values / nanoJansky.values,
496 atol=1e-13,
497 rtol=0))
499 # Test functors against the values computed above.
500 self._testLocalPhotometryFunctors(LocalNanojansky,
501 parq,
502 nanoJansky)
503 self._testLocalPhotometryFunctors(LocalNanojanskyErr,
504 parq,
505 nanoJanskyErr)
506 self._testLocalPhotometryFunctors(LocalMagnitude,
507 parq,
508 mag)
509 self._testLocalPhotometryFunctors(LocalMagnitudeErr,
510 parq,
511 magErr)
513 def _testLocalPhotometryFunctors(self, functor, parq, testValues):
514 func = functor("base_PsfFlux_instFlux",
515 "base_PsfFlux_instFluxErr",
516 "base_LocalPhotoCalib",
517 "base_LocalPhotoCalibErr")
518 val = self._funcVal(func, parq)
519 self.assertTrue(np.allclose(testValues.values,
520 val.values,
521 atol=1e-13,
522 rtol=0))
524 def testDipPhotometry(self):
525 """Test calibrated flux calculations for dipoles."""
526 fluxNeg = -100
527 fluxPos = 101
528 fluxErr = 10
529 calib = 10
530 calibErr = 1
532 # compute expected values.
533 absMean = 0.5*(fluxPos - fluxNeg)*calib
534 absDiff = (fluxNeg + fluxPos)*calib
535 absMeanErr = 0.5*np.sqrt(2*(fluxErr*calib)**2
536 + ((fluxPos - fluxNeg)*calibErr)**2)
537 absDiffErr = np.sqrt(2*(fluxErr*calib)**2
538 + ((fluxPos + fluxNeg)*calibErr)**2)
540 self.dataDict["ip_diffim_DipoleFluxNeg_instFlux"] = np.full(self.nRecords, fluxNeg)
541 self.dataDict["ip_diffim_DipoleFluxNeg_instFluxErr"] = np.full(self.nRecords, fluxErr)
542 self.dataDict["ip_diffim_DipoleFluxPos_instFlux"] = np.full(self.nRecords, fluxPos)
543 self.dataDict["ip_diffim_DipoleFluxPos_instFluxErr"] = np.full(self.nRecords, fluxErr)
544 self.dataDict["base_LocalPhotoCalib"] = np.full(self.nRecords, calib)
545 self.dataDict["base_LocalPhotoCalibErr"] = np.full(self.nRecords,
546 calibErr)
548 parq = self.simulateMultiParquet(self.dataDict)
549 func = LocalDipoleMeanFlux("ip_diffim_DipoleFluxPos_instFlux",
550 "ip_diffim_DipoleFluxNeg_instFlux",
551 "ip_diffim_DipoleFluxPos_instFluxErr",
552 "ip_diffim_DipoleFluxNeg_instFluxErr",
553 "base_LocalPhotoCalib",
554 "base_LocalPhotoCalibErr")
555 val = self._funcVal(func, parq)
556 self.assertTrue(np.allclose(val.values,
557 absMean,
558 atol=1e-13,
559 rtol=0))
561 func = LocalDipoleMeanFluxErr("ip_diffim_DipoleFluxPos_instFlux",
562 "ip_diffim_DipoleFluxNeg_instFlux",
563 "ip_diffim_DipoleFluxPos_instFluxErr",
564 "ip_diffim_DipoleFluxNeg_instFluxErr",
565 "base_LocalPhotoCalib",
566 "base_LocalPhotoCalibErr")
567 val = self._funcVal(func, parq)
568 self.assertTrue(np.allclose(val.values,
569 absMeanErr,
570 atol=1e-13,
571 rtol=0))
573 func = LocalDipoleDiffFlux("ip_diffim_DipoleFluxPos_instFlux",
574 "ip_diffim_DipoleFluxNeg_instFlux",
575 "ip_diffim_DipoleFluxPos_instFluxErr",
576 "ip_diffim_DipoleFluxNeg_instFluxErr",
577 "base_LocalPhotoCalib",
578 "base_LocalPhotoCalibErr")
579 val = self._funcVal(func, parq)
580 self.assertTrue(np.allclose(val.values,
581 absDiff,
582 atol=1e-13,
583 rtol=0))
585 func = LocalDipoleDiffFluxErr("ip_diffim_DipoleFluxPos_instFlux",
586 "ip_diffim_DipoleFluxNeg_instFlux",
587 "ip_diffim_DipoleFluxPos_instFluxErr",
588 "ip_diffim_DipoleFluxNeg_instFluxErr",
589 "base_LocalPhotoCalib",
590 "base_LocalPhotoCalibErr")
591 val = self._funcVal(func, parq)
592 self.assertTrue(np.allclose(val.values,
593 absDiffErr,
594 atol=1e-13,
595 rtol=0))
597 def testConvertPixelToArcseconds(self):
598 """Test calculations of the pixel scale and conversions of pixel to
599 arcseconds.
600 """
601 dipoleSep = 10
602 ixx = 10
603 testPixelDeltas = np.random.uniform(-100, 100, size=(self.nRecords, 2))
604 import lsst.afw.table as afwTable
605 localWcsPlugin = measBase.EvaluateLocalWcsPlugin(
606 None,
607 "base_LocalWcs",
608 afwTable.SourceTable.makeMinimalSchema(),
609 None)
610 for dec in np.linspace(-90, 90, 10):
611 for x, y in zip(np.random.uniform(2 * 1109.99981456774, size=10),
612 np.random.uniform(2 * 560.018167811613, size=10)):
613 center = geom.Point2D(x, y)
614 wcs = self._makeWcs(dec)
615 skyOrigin = wcs.pixelToSky(center)
617 linAffMatrix = localWcsPlugin.makeLocalTransformMatrix(wcs,
618 center)
619 self.dataDict["dipoleSep"] = np.full(self.nRecords, dipoleSep)
620 self.dataDict["ixx"] = np.full(self.nRecords, ixx)
621 self.dataDict["slot_Centroid_x"] = np.full(self.nRecords, x)
622 self.dataDict["slot_Centroid_y"] = np.full(self.nRecords, y)
623 self.dataDict["someCentroid_x"] = x + testPixelDeltas[:, 0]
624 self.dataDict["someCentroid_y"] = y + testPixelDeltas[:, 1]
625 self.dataDict["base_LocalWcs_CDMatrix_1_1"] = np.full(self.nRecords,
626 linAffMatrix[0, 0])
627 self.dataDict["base_LocalWcs_CDMatrix_1_2"] = np.full(self.nRecords,
628 linAffMatrix[0, 1])
629 self.dataDict["base_LocalWcs_CDMatrix_2_1"] = np.full(self.nRecords,
630 linAffMatrix[1, 0])
631 self.dataDict["base_LocalWcs_CDMatrix_2_2"] = np.full(self.nRecords,
632 linAffMatrix[1, 1])
633 parq = self.simulateMultiParquet(self.dataDict)
634 func = LocalWcs("base_LocalWcs_CDMatrix_1_1",
635 "base_LocalWcs_CDMatrix_1_2",
636 "base_LocalWcs_CDMatrix_2_1",
637 "base_LocalWcs_CDMatrix_2_2")
638 df = parq.toDataFrame(columns={"dataset": "meas",
639 "band": "g",
640 "columns": ["dipoleSep",
641 "slot_Centroid_x",
642 "slot_Centroid_y",
643 "someCentroid_x",
644 "someCentroid_y",
645 "base_LocalWcs_CDMatrix_1_1",
646 "base_LocalWcs_CDMatrix_1_2",
647 "base_LocalWcs_CDMatrix_2_1",
648 "base_LocalWcs_CDMatrix_2_2"]})
650 # Exercise the full set of functions in LocalWcs.
651 sepRadians = func.getSkySeperationFromPixel(
652 df[("meas", "g", "someCentroid_x")] - df[("meas", "g", "slot_Centroid_x")],
653 df[("meas", "g", "someCentroid_y")] - df[("meas", "g", "slot_Centroid_y")],
654 0.0,
655 0.0,
656 df[("meas", "g", "base_LocalWcs_CDMatrix_1_1")],
657 df[("meas", "g", "base_LocalWcs_CDMatrix_1_2")],
658 df[("meas", "g", "base_LocalWcs_CDMatrix_2_1")],
659 df[("meas", "g", "base_LocalWcs_CDMatrix_2_2")])
661 # Test functor values against afw SkyWcs computations.
662 for centX, centY, sep in zip(testPixelDeltas[:, 0],
663 testPixelDeltas[:, 1],
664 sepRadians.values):
665 afwSepRadians = skyOrigin.separation(
666 wcs.pixelToSky(x + centX, y + centY)).asRadians()
667 self.assertAlmostEqual(1 - sep / afwSepRadians, 0, places=6)
669 # Test the pixel scale computation.
670 func = ComputePixelScale("base_LocalWcs_CDMatrix_1_1",
671 "base_LocalWcs_CDMatrix_1_2",
672 "base_LocalWcs_CDMatrix_2_1",
673 "base_LocalWcs_CDMatrix_2_2")
674 pixelScale = self._funcVal(func, parq)
675 self.assertTrue(np.allclose(
676 wcs.getPixelScale(center).asArcseconds(),
677 pixelScale.values,
678 rtol=1e-8,
679 atol=0))
681 # Test pixel -> arcsec conversion.
682 func = ConvertPixelToArcseconds("dipoleSep",
683 "base_LocalWcs_CDMatrix_1_1",
684 "base_LocalWcs_CDMatrix_1_2",
685 "base_LocalWcs_CDMatrix_2_1",
686 "base_LocalWcs_CDMatrix_2_2")
687 val = self._funcVal(func, parq)
688 self.assertTrue(np.allclose(pixelScale.values * dipoleSep,
689 val.values,
690 atol=1e-16,
691 rtol=1e-16))
693 # Test pixel^2 -> arcsec^2 conversion.
694 func = ConvertPixelSqToArcsecondsSq("ixx",
695 "base_LocalWcs_CDMatrix_1_1",
696 "base_LocalWcs_CDMatrix_1_2",
697 "base_LocalWcs_CDMatrix_2_1",
698 "base_LocalWcs_CDMatrix_2_2")
699 val = self._funcVal(func, parq)
700 self.assertTrue(np.allclose(pixelScale.values ** 2 * dipoleSep,
701 val.values,
702 atol=1e-16,
703 rtol=1e-16))
705 def _makeWcs(self, dec=53.1595451514076):
706 """Create a wcs from real CFHT values.
708 Returns
709 -------
710 wcs : `lsst.afw.geom`
711 Created wcs.
712 """
713 metadata = dafBase.PropertySet()
715 metadata.set("SIMPLE", "T")
716 metadata.set("BITPIX", -32)
717 metadata.set("NAXIS", 2)
718 metadata.set("NAXIS1", 1024)
719 metadata.set("NAXIS2", 1153)
720 metadata.set("RADECSYS", 'FK5')
721 metadata.set("EQUINOX", 2000.)
723 metadata.setDouble("CRVAL1", 215.604025685476)
724 metadata.setDouble("CRVAL2", dec)
725 metadata.setDouble("CRPIX1", 1109.99981456774)
726 metadata.setDouble("CRPIX2", 560.018167811613)
727 metadata.set("CTYPE1", 'RA---SIN')
728 metadata.set("CTYPE2", 'DEC--SIN')
730 metadata.setDouble("CD1_1", 5.10808596133527E-05)
731 metadata.setDouble("CD1_2", 1.85579539217196E-07)
732 metadata.setDouble("CD2_2", -5.10281493481982E-05)
733 metadata.setDouble("CD2_1", -8.27440751733828E-07)
735 return afwGeom.makeSkyWcs(metadata)
737 def testRatio(self):
738 """Test the ratio functor where one of the functors should be junk.
739 """
740 self.dataDict["base_PsfFlux_instFlux"] = np.full(self.nRecords, 1000)
741 self.dataDict["base_PsfFlux_instFluxErr"] = np.full(self.nRecords, 100)
742 parq = self.simulateMultiParquet(self.dataDict)
744 func = Ratio("base_PsfFlux_instFlux", "base_PsfFlux_instFluxErr")
746 val = self._funcVal(func, parq)
747 self.assertTrue(np.allclose(np.full(self.nRecords, 10),
748 val.values,
749 atol=1e-16,
750 rtol=1e-16))
752 def testHtm(self):
753 """Test that HtmIndxes are created as expected.
754 """
755 parq = self.simulateMultiParquet(self.dataDict)
756 func = HtmIndex20("coord_ra", "coord_dec")
758 val = self._funcVal(func, parq)
759 # Test that the HtmIds come out as the ra/dec in dataDict.
760 self.assertTrue(np.all(np.equal(
761 val.values,
762 [14924528684992, 14924528689697, 14924528501716, 14924526434259,
763 14924526433879])))
766class MyMemoryTestCase(lsst.utils.tests.MemoryTestCase):
767 pass
770def setup_module(module):
771 lsst.utils.tests.init()
774if __name__ == "__main__": 774 ↛ 775line 774 didn't jump to line 775, because the condition on line 774 was never true
775 lsst.utils.tests.init()
776 unittest.main()