Coverage for tests/test_photoCal.py: 22%

118 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-01-27 02:45 -0800

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 

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" 

102 

103 def tearDown(self): 

104 del self.srcCat 

105 del self.exposure 

106 del self.refObjLoader 

107 del self.log 

108 

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] 

115 

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 

132 

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) 

142 

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) 

152 

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) 

160 

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

166 

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 

173 

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 

182 

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

195 

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) 

200 

201 

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

203 pass 

204 

205 

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