Coverage for tests/test_photoCal.py: 22%
118 statements
« prev ^ index » next coverage.py v6.5.0, created at 2022-10-26 02:42 -0700
« prev ^ index » next coverage.py v6.5.0, created at 2022-10-26 02:42 -0700
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
99 # The test and associated data have been prepared on the basis that we
100 # use the PsfFlux to perform photometry.
101 self.config.fluxField = "base_PsfFlux_instFlux"
103 def tearDown(self):
104 del self.srcCat
105 del self.exposure
106 del self.refObjLoader
107 del self.log
109 def _runTask(self):
110 """All the common setup to actually test the results"""
111 task = PhotoCalTask(self.refObjLoader, config=self.config, schema=self.srcCat.schema)
112 pCal = task.run(exposure=self.exposure, sourceCat=self.srcCat)
113 matches = pCal.matches
114 refFluxField = pCal.arrays.refFluxFieldList[0]
116 # These are *all* the matches; we don't really expect to do that well.
117 diff = []
118 for m in matches:
119 refFlux = m[0].get(refFluxField) # reference catalog flux
120 if refFlux <= 0:
121 continue
122 refMag = u.Quantity(refFlux, u.nJy).to_value(u.ABmag)
123 instFlux = m[1].getPsfInstFlux() # Instrumental Flux
124 if instFlux <= 0:
125 continue
126 instMag = pCal.photoCalib.instFluxToMagnitude(instFlux) # Instrumental mag
127 diff.append(instMag - refMag)
128 self.diff = np.array(diff)
129 # Differences of matched objects that were used in the fit.
130 self.zp = pCal.photoCalib.instFluxToMagnitude(1.)
131 self.fitdiff = pCal.arrays.srcMag + self.zp - pCal.arrays.refMag
133 def testFlags(self):
134 """test that all the calib_photometry flags are set to reasonable values"""
135 schema = self.srcCat.schema
136 task = PhotoCalTask(self.refObjLoader, config=self.config, schema=schema)
137 mapper = afwTable.SchemaMapper(self.srcCat.schema, schema)
138 cat = afwTable.SourceCatalog(schema)
139 for name in self.srcCat.schema.getNames():
140 mapper.addMapping(self.srcCat.schema.find(name).key)
141 cat.extend(self.srcCat, mapper=mapper)
143 # test that by default, no stars are reserved and all used are candidates
144 task.run(exposure=self.exposure, sourceCat=cat)
145 used = 0
146 for source in cat:
147 if source.get("calib_photometry_used"):
148 used += 1
149 self.assertFalse(source.get("calib_photometry_reserved"))
150 # test that some are actually used
151 self.assertGreater(used, 0)
153 def testZeroPoint(self):
154 """ Test to see if we can compute a photometric zeropoint given a reference task"""
155 self._runTask()
156 self.assertGreater(len(self.diff), 50)
157 self.log.info('%i magnitude differences; mean difference %g; mean abs diff %g' %
158 (len(self.diff), np.mean(self.diff), np.mean(np.abs(self.diff))))
159 self.assertLess(np.mean(self.diff), 0.6)
161 # Differences of matched objects that were used in the fit.
162 self.log.debug('zeropoint: %g', self.zp)
163 self.log.debug('number of sources used in fit: %i', len(self.fitdiff))
164 self.log.debug('rms diff: %g', np.mean(self.fitdiff**2)**0.5)
165 self.log.debug('median abs(diff): %g', np.median(np.abs(self.fitdiff)))
167 # zeropoint: 31.3145
168 # number of sources used in fit: 65
169 # median diff: -0.009681
170 # mean diff: 0.00331871
171 # median abs(diff): 0.0368904
172 # mean abs(diff): 0.0516589
174 self.assertLess(abs(self.zp - 31.3145), 0.05)
175 self.assertGreater(len(self.fitdiff), 50)
176 # Tolerances are somewhat arbitrary; they're set simply to avoid regressions, and
177 # are not based on we'd expect to get given the data quality.
178 lq, uq = np.percentile(self.fitdiff, (25, 75))
179 rms = 0.741*(uq - lq) # Convert IQR to stdev assuming a Gaussian
180 self.assertLess(rms, 0.07) # rms difference
181 self.assertLess(np.median(np.abs(self.fitdiff)), 0.06) # median absolution difference
183 def testColorTerms(self):
184 """ Test to see if we can apply colorterm corrections while computing photometric zeropoints"""
185 # Turn colorterms on. The colorterm library used here is simple - we just apply a 1 mag
186 # color-independentcolorterm correction to everything. This should change the photometric zeropoint.
187 # by 1 mag.
188 self.config.applyColorTerms = True
189 self.config.colorterms = testColorterms
190 self.config.photoCatName = "testglob" # Check glo expansion
191 # zerPointOffset is the offset in the zeropoint that we expect from a uniform (i.e. color-independent)
192 # colorterm correction.
193 zeroPointOffset = testColorterms.data['test*'].data['test-i'].c0
194 self._runTask()
196 self.assertLess(np.mean(self.diff), 0.6 + zeroPointOffset)
197 self.log.debug('zeropoint: %g', self.zp)
198 # zeropoint: 32.3145
199 self.assertLess(abs(self.zp - (31.3145 + zeroPointOffset)), 0.05)
202class MemoryTester(lsst.utils.tests.MemoryTestCase):
203 pass
206if __name__ == "__main__": 206 ↛ 207line 206 didn't jump to line 207, because the condition on line 206 was never true
207 lsst.utils.tests.init()
208 unittest.main()