Coverage for tests/test_photoCal.py: 21%
119 statements
« prev ^ index » next coverage.py v6.5.0, created at 2023-06-07 12:50 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2023-06-07 12:50 +0000
1#
2# LSST Data Management System
3# Copyright 2008, 2009, 2010 LSST Corporation.
4#
5# This product includes software developed by the
6# LSST Project (http://www.lsst.org/).
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the LSST License Statement and
19# the GNU General Public License along with this program. If not,
20# see <http://www.lsstcorp.org/LegalNotices/>.
21#
22import os
23import unittest
24import logging
25import glob
27import numpy as np
28import astropy.units as u
30import lsst.geom as geom
31import lsst.afw.table as afwTable
32import lsst.afw.image as afwImage
33import lsst.utils.tests
34from lsst.utils import getPackageDir
35from lsst.pipe.tasks.photoCal import PhotoCalTask, PhotoCalConfig
36from lsst.pipe.tasks.colorterms import Colorterm, ColortermDict, ColortermLibrary
37from lsst.utils.logging import TRACE
38from lsst.meas.algorithms.testUtils import MockReferenceObjectLoaderFromFiles
40RefCatDir = os.path.join(getPackageDir("pipe_tasks"), "tests", "data", "sdssrefcat")
42testColorterms = ColortermLibrary(data={
43 "test*": ColortermDict(data={
44 "test-g": Colorterm(primary="g", secondary="r", c0=0.00, c1=0.00),
45 "test-r": Colorterm(primary="r", secondary="i", c0=0.00, c1=0.00, c2=0.00),
46 "test-i": Colorterm(primary="i", secondary="z", c0=1.00, c1=0.00, c2=0.00),
47 "test-z": Colorterm(primary="z", secondary="i", c0=0.00, c1=0.00, c2=0.00),
48 })
49})
52def setup_module(module):
53 lsst.utils.tests.init()
56class PhotoCalTest(unittest.TestCase):
58 def setUp(self):
60 # Load sample input from disk
61 testDir = os.path.dirname(__file__)
62 self.srcCat = afwTable.SourceCatalog.readFits(
63 os.path.join(testDir, "data", "v695833-e0-c000.xy.fits"))
65 self.srcCat["slot_ApFlux_instFluxErr"] = 1
66 self.srcCat["slot_PsfFlux_instFluxErr"] = 1
68 # The .xy.fits file has sources in the range ~ [0,2000],[0,4500]
69 # which is bigger than the exposure
70 self.bbox = geom.Box2I(geom.Point2I(0, 0), geom.Extent2I(2048, 4612))
71 smallExposure = afwImage.ExposureF(os.path.join(testDir, "data", "v695833-e0-c000-a00.sci.fits"))
72 self.exposure = afwImage.ExposureF(self.bbox)
73 self.exposure.setWcs(smallExposure.getWcs())
74 self.exposure.setFilter(afwImage.FilterLabel(band="i", physical="test-i"))
75 self.exposure.setPhotoCalib(smallExposure.getPhotoCalib())
77 coordKey = self.srcCat.getCoordKey()
78 centroidKey = self.srcCat.getCentroidSlot().getMeasKey()
79 wcs = self.exposure.getWcs()
80 for src in self.srcCat:
81 src.set(coordKey, wcs.pixelToSky(src.get(centroidKey)))
83 # Make a reference loader
84 filenames = sorted(glob.glob(os.path.join(RefCatDir, 'ref_cats', 'cal_ref_cat', '??????.fits')))
85 self.refObjLoader = MockReferenceObjectLoaderFromFiles(filenames, htmLevel=8)
86 self.log = logging.getLogger('lsst.testPhotoCal')
87 self.log.setLevel(TRACE)
89 self.config = PhotoCalConfig()
90 self.config.match.matchRadius = 0.5
91 self.config.match.referenceSelection.doMagLimit = True
92 self.config.match.referenceSelection.magLimit.maximum = 22.0
93 self.config.match.referenceSelection.magLimit.fluxField = "i_flux"
94 self.config.match.referenceSelection.doFlags = True
95 self.config.match.referenceSelection.flags.good = ['photometric']
96 self.config.match.referenceSelection.flags.bad = ['resolved']
97 self.config.match.sourceSelection.doUnresolved = False # Don't have star/galaxy in the srcCat
98 self.config.match.sourceSelection.doRequirePrimary = False # Don't have detect_isPrimary in srcCat
100 # The test and associated data have been prepared on the basis that we
101 # use the PsfFlux to perform photometry.
102 self.config.fluxField = "base_PsfFlux_instFlux"
104 def tearDown(self):
105 del self.srcCat
106 del self.exposure
107 del self.refObjLoader
108 del self.log
110 def _runTask(self):
111 """All the common setup to actually test the results"""
112 task = PhotoCalTask(self.refObjLoader, config=self.config, schema=self.srcCat.schema)
113 pCal = task.run(exposure=self.exposure, sourceCat=self.srcCat)
114 matches = pCal.matches
115 refFluxField = pCal.arrays.refFluxFieldList[0]
117 # These are *all* the matches; we don't really expect to do that well.
118 diff = []
119 for m in matches:
120 refFlux = m[0].get(refFluxField) # reference catalog flux
121 if refFlux <= 0:
122 continue
123 refMag = u.Quantity(refFlux, u.nJy).to_value(u.ABmag)
124 instFlux = m[1].getPsfInstFlux() # Instrumental Flux
125 if instFlux <= 0:
126 continue
127 instMag = pCal.photoCalib.instFluxToMagnitude(instFlux) # Instrumental mag
128 diff.append(instMag - refMag)
129 self.diff = np.array(diff)
130 # Differences of matched objects that were used in the fit.
131 self.zp = pCal.photoCalib.instFluxToMagnitude(1.)
132 self.fitdiff = pCal.arrays.srcMag + self.zp - pCal.arrays.refMag
134 def testFlags(self):
135 """test that all the calib_photometry flags are set to reasonable values"""
136 schema = self.srcCat.schema
137 task = PhotoCalTask(self.refObjLoader, config=self.config, schema=schema)
138 mapper = afwTable.SchemaMapper(self.srcCat.schema, schema)
139 cat = afwTable.SourceCatalog(schema)
140 for name in self.srcCat.schema.getNames():
141 mapper.addMapping(self.srcCat.schema.find(name).key)
142 cat.extend(self.srcCat, mapper=mapper)
144 # test that by default, no stars are reserved and all used are candidates
145 task.run(exposure=self.exposure, sourceCat=cat)
146 used = 0
147 for source in cat:
148 if source.get("calib_photometry_used"):
149 used += 1
150 self.assertFalse(source.get("calib_photometry_reserved"))
151 # test that some are actually used
152 self.assertGreater(used, 0)
154 def testZeroPoint(self):
155 """ Test to see if we can compute a photometric zeropoint given a reference task"""
156 self._runTask()
157 self.assertGreater(len(self.diff), 50)
158 self.log.info('%i magnitude differences; mean difference %g; mean abs diff %g' %
159 (len(self.diff), np.mean(self.diff), np.mean(np.abs(self.diff))))
160 self.assertLess(np.mean(self.diff), 0.6)
162 # Differences of matched objects that were used in the fit.
163 self.log.debug('zeropoint: %g', self.zp)
164 self.log.debug('number of sources used in fit: %i', len(self.fitdiff))
165 self.log.debug('rms diff: %g', np.mean(self.fitdiff**2)**0.5)
166 self.log.debug('median abs(diff): %g', np.median(np.abs(self.fitdiff)))
168 # zeropoint: 31.3145
169 # number of sources used in fit: 65
170 # median diff: -0.009681
171 # mean diff: 0.00331871
172 # median abs(diff): 0.0368904
173 # mean abs(diff): 0.0516589
175 self.assertLess(abs(self.zp - 31.3145), 0.05)
176 self.assertGreater(len(self.fitdiff), 50)
177 # Tolerances are somewhat arbitrary; they're set simply to avoid regressions, and
178 # are not based on we'd expect to get given the data quality.
179 lq, uq = np.percentile(self.fitdiff, (25, 75))
180 rms = 0.741*(uq - lq) # Convert IQR to stdev assuming a Gaussian
181 self.assertLess(rms, 0.07) # rms difference
182 self.assertLess(np.median(np.abs(self.fitdiff)), 0.06) # median absolution difference
184 def testColorTerms(self):
185 """ Test to see if we can apply colorterm corrections while computing photometric zeropoints"""
186 # Turn colorterms on. The colorterm library used here is simple - we just apply a 1 mag
187 # color-independentcolorterm correction to everything. This should change the photometric zeropoint.
188 # by 1 mag.
189 self.config.applyColorTerms = True
190 self.config.colorterms = testColorterms
191 self.config.photoCatName = "testglob" # Check glo expansion
192 # zerPointOffset is the offset in the zeropoint that we expect from a uniform (i.e. color-independent)
193 # colorterm correction.
194 zeroPointOffset = testColorterms.data['test*'].data['test-i'].c0
195 self._runTask()
197 self.assertLess(np.mean(self.diff), 0.6 + zeroPointOffset)
198 self.log.debug('zeropoint: %g', self.zp)
199 # zeropoint: 32.3145
200 self.assertLess(abs(self.zp - (31.3145 + zeroPointOffset)), 0.05)
203class MemoryTester(lsst.utils.tests.MemoryTestCase):
204 pass
207if __name__ == "__main__": 207 ↛ 208line 207 didn't jump to line 208, because the condition on line 207 was never true
208 lsst.utils.tests.init()
209 unittest.main()