Coverage for tests/test_photoCal.py: 21%

119 statements  

« prev     ^ index     » next       coverage.py v7.5.1, created at 2024-05-10 03:09 -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 

26 

27import numpy as np 

28import astropy.units as u 

29 

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 

39 

40RefCatDir = os.path.join(getPackageDir("pipe_tasks"), "tests", "data", "sdssrefcat") 

41 

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}) 

50 

51 

52def setup_module(module): 

53 lsst.utils.tests.init() 

54 

55 

56class PhotoCalTest(unittest.TestCase): 

57 

58 def setUp(self): 

59 

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")) 

64 

65 self.srcCat["slot_ApFlux_instFluxErr"] = 1 

66 self.srcCat["slot_PsfFlux_instFluxErr"] = 1 

67 

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()) 

76 

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))) 

82 

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) 

88 

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 

99 

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" 

103 

104 def tearDown(self): 

105 del self.srcCat 

106 del self.exposure 

107 del self.refObjLoader 

108 del self.log 

109 

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] 

116 

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 

133 

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) 

143 

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) 

153 

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) 

161 

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))) 

167 

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 

174 

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 

183 

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() 

196 

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) 

201 

202 

203class MemoryTester(lsst.utils.tests.MemoryTestCase): 

204 pass 

205 

206 

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()