Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

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# (https://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 <https://www.gnu.org/licenses/>. 

21import warnings 

22import numpy as np 

23import astropy.units as units 

24from astropy.time import Time 

25from astropy.coordinates import AltAz, SkyCoord, EarthLocation 

26from lsst.daf.base import DateTime 

27 

28import lsst.pipe.base as pipeBase 

29import lsst.pex.config as pexConfig 

30import lsst.afw.math as afwMath 

31import lsst.afw.image as afwImage 

32import lsst.geom 

33 

34 

35__all__ = ("ComputeExposureSummaryStatsTask", "ComputeExposureSummaryStatsConfig") 

36 

37 

38class ComputeExposureSummaryStatsConfig(pexConfig.Config): 

39 """Config for ComputeExposureSummaryTask""" 

40 sigmaClip = pexConfig.Field( 

41 dtype=float, 

42 doc="Sigma for outlier rejection for sky noise.", 

43 default=3.0, 

44 ) 

45 clipIter = pexConfig.Field( 

46 dtype=int, 

47 doc="Number of iterations of outlier rejection for sky noise.", 

48 default=2, 

49 ) 

50 badMaskPlanes = pexConfig.ListField( 

51 dtype=str, 

52 doc="Mask planes that, if set, the associated pixel should not be included sky noise calculation.", 

53 default=("NO_DATA", "SUSPECT"), 

54 ) 

55 

56 

57class ComputeExposureSummaryStatsTask(pipeBase.Task): 

58 """Task to compute exposure summary statistics. 

59 

60 This task computes various quantities suitable for DPDD and other 

61 downstream processing at the detector centers, including: 

62 - psfSigma 

63 - psfArea 

64 - psfIxx 

65 - psfIyy 

66 - psfIxy 

67 - ra 

68 - decl 

69 - zenithDistance 

70 - zeroPoint 

71 - skyBg 

72 - skyNoise 

73 - meanVar 

74 - raCorners 

75 - decCorners 

76 """ 

77 ConfigClass = ComputeExposureSummaryStatsConfig 

78 _DefaultName = "computeExposureSummaryStats" 

79 

80 @pipeBase.timeMethod 

81 def run(self, exposure, sources, background): 

82 """Measure exposure statistics from the exposure, sources, and background. 

83 

84 Parameters 

85 ---------- 

86 exposure : `lsst.afw.image.ExposureF` 

87 sources : `lsst.afw.table.SourceCatalog` 

88 background : `lsst.afw.math.BackgroundList` 

89 

90 Returns 

91 ------- 

92 summary : `lsst.afw.image.ExposureSummary` 

93 """ 

94 self.log.info("Measuring exposure statistics") 

95 

96 bbox = exposure.getBBox() 

97 

98 psf = exposure.getPsf() 

99 if psf is not None: 

100 shape = psf.computeShape(bbox.getCenter()) 

101 psfSigma = shape.getDeterminantRadius() 

102 psfIxx = shape.getIxx() 

103 psfIyy = shape.getIyy() 

104 psfIxy = shape.getIxy() 

105 im = psf.computeKernelImage(bbox.getCenter()) 

106 # The calculation of effective psf area is taken from 

107 # meas_base/src/PsfFlux.cc#L112. See 

108 # https://github.com/lsst/meas_base/blob/ 

109 # 750bffe6620e565bda731add1509507f5c40c8bb/src/PsfFlux.cc#L112 

110 psfArea = np.sum(im.array)/np.sum(im.array**2.) 

111 else: 

112 psfSigma = np.nan 

113 psfIxx = np.nan 

114 psfIyy = np.nan 

115 psfIxy = np.nan 

116 psfArea = np.nan 

117 

118 wcs = exposure.getWcs() 

119 if wcs is not None: 

120 sph_pts = wcs.pixelToSky(lsst.geom.Box2D(bbox).getCorners()) 

121 raCorners = [float(sph.getRa().asDegrees()) for sph in sph_pts] 

122 decCorners = [float(sph.getDec().asDegrees()) for sph in sph_pts] 

123 

124 sph_pt = wcs.pixelToSky(bbox.getCenter()) 

125 ra = sph_pt.getRa().asDegrees() 

126 decl = sph_pt.getDec().asDegrees() 

127 else: 

128 raCorners = [float(np.nan)]*4 

129 decCorners = [float(np.nan)]*4 

130 ra = np.nan 

131 decl = np.nan 

132 

133 photoCalib = exposure.getPhotoCalib() 

134 if photoCalib is not None: 

135 zeroPoint = 2.5*np.log10(photoCalib.getInstFluxAtZeroMagnitude()) 

136 else: 

137 zeroPoint = np.nan 

138 

139 visitInfo = exposure.getInfo().getVisitInfo() 

140 date = visitInfo.getDate() 

141 

142 if date.isValid(): 

143 # We compute the zenithDistance at the center of the detector rather 

144 # than use the boresight value available via the visitInfo, because 

145 # the zenithDistance may vary significantly over a large field of view. 

146 observatory = visitInfo.getObservatory() 

147 loc = EarthLocation(lat=observatory.getLatitude().asDegrees()*units.deg, 

148 lon=observatory.getLongitude().asDegrees()*units.deg, 

149 height=observatory.getElevation()*units.m) 

150 obstime = Time(visitInfo.getDate().get(system=DateTime.MJD), 

151 location=loc, format='mjd') 

152 coord = SkyCoord(ra*units.degree, decl*units.degree, obstime=obstime, location=loc) 

153 with warnings.catch_warnings(): 

154 warnings.simplefilter('ignore') 

155 altaz = coord.transform_to(AltAz) 

156 

157 zenithDistance = altaz.alt.degree 

158 else: 

159 zenithDistance = np.nan 

160 

161 if background is not None: 

162 bgStats = (bg[0].getStatsImage().getImage().array 

163 for bg in background) 

164 skyBg = sum(np.median(bg[np.isfinite(bg)]) for bg in bgStats) 

165 else: 

166 skyBg = np.nan 

167 

168 statsCtrl = afwMath.StatisticsControl() 

169 statsCtrl.setNumSigmaClip(self.config.sigmaClip) 

170 statsCtrl.setNumIter(self.config.clipIter) 

171 statsCtrl.setAndMask(afwImage.Mask.getPlaneBitMask(self.config.badMaskPlanes)) 

172 statsCtrl.setNanSafe(True) 

173 

174 statObj = afwMath.makeStatistics(exposure.getMaskedImage(), afwMath.STDEVCLIP, 

175 statsCtrl) 

176 skyNoise, _ = statObj.getResult(afwMath.STDEVCLIP) 

177 

178 statObj = afwMath.makeStatistics(exposure.getMaskedImage().getVariance(), 

179 exposure.getMaskedImage().getMask(), 

180 afwMath.MEANCLIP, statsCtrl) 

181 meanVar, _ = statObj.getResult(afwMath.MEANCLIP) 

182 

183 summary = afwImage.ExposureSummaryStats(psfSigma=float(psfSigma), 

184 psfArea=float(psfArea), 

185 psfIxx=float(psfIxx), 

186 psfIyy=float(psfIyy), 

187 psfIxy=float(psfIxy), 

188 ra=float(ra), 

189 decl=float(decl), 

190 zenithDistance=float(zenithDistance), 

191 zeroPoint=float(zeroPoint), 

192 skyBg=float(skyBg), 

193 skyNoise=float(skyNoise), 

194 meanVar=float(meanVar), 

195 raCorners=raCorners, 

196 decCorners=decCorners) 

197 

198 return summary