Coverage for python/lsst/pipe/tasks/scaleZeroPoint.py: 42%

85 statements  

« prev     ^ index     » next       coverage.py v6.4.1, created at 2022-06-26 01:41 -0700

1# 

2# LSST Data Management System 

3# Copyright 2008, 2009, 2010, 2011, 2012 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 numpy 

23import lsst.geom as geom 

24import lsst.afw.image as afwImage 

25import lsst.pex.config as pexConfig 

26import lsst.pipe.base as pipeBase 

27from lsst.pipe.tasks.selectImages import BaseSelectImagesTask 

28 

29__all__ = ["ImageScaler", "SpatialImageScaler", "ScaleZeroPointTask"] 

30 

31 

32class ImageScaler: 

33 """A class that scales an image 

34 

35 This version uses a single scalar. Fancier versions may use a spatially varying scale. 

36 """ 

37 

38 def __init__(self, scale=1.0): 

39 """Construct an ImageScaler 

40 

41 @param[in] scale: scale correction to apply (see scaleMaskedImage); 

42 """ 

43 self._scale = scale 

44 

45 def scaleMaskedImage(self, maskedImage): 

46 """Scale the specified image or masked image in place. 

47 

48 @param[in,out] maskedImage: masked image to scale 

49 """ 

50 maskedImage *= self._scale 

51 

52 

53class SpatialImageScaler(ImageScaler): 

54 """Multiplicative image scaler using interpolation over a grid of points. 

55 

56 Contains the x, y positions in tract coordinates and the scale factors. 

57 Interpolates only when scaleMaskedImage() or getInterpImage() is called. 

58 

59 Currently the only type of 'interpolation' implemented is CONSTANT which calculates the mean. 

60 """ 

61 

62 def __init__(self, interpStyle, xList, yList, scaleList): 

63 """Constructor 

64 

65 @param[in] interpStyle: interpolation style (CONSTANT is only option) 

66 @param[in] xList: list of X pixel positions 

67 @param[in] yList: list of Y pixel positions 

68 @param[in] scaleList: list of multiplicative scale factors at (x,y) 

69 

70 @raise RuntimeError if the lists have different lengths 

71 """ 

72 if len(xList) != len(yList) or len(xList) != len(scaleList): 

73 raise RuntimeError( 

74 "len(xList)=%s len(yList)=%s, len(scaleList)=%s but all lists must have the same length" % 

75 (len(xList), len(yList), len(scaleList))) 

76 

77 # Eventually want this do be: self.interpStyle = getattr(afwMath.Interpolate2D, interpStyle) 

78 self._xList = xList 

79 self._yList = yList 

80 self._scaleList = scaleList 

81 

82 def scaleMaskedImage(self, maskedImage): 

83 """Apply scale correction to the specified masked image 

84 

85 @param[in,out] image to scale; scale is applied in place 

86 """ 

87 scale = self.getInterpImage(maskedImage.getBBox()) 

88 maskedImage *= scale 

89 

90 def getInterpImage(self, bbox): 

91 """Return an image containing the scale correction with same bounding box as supplied. 

92 

93 @param[in] bbox: integer bounding box for image (geom.Box2I) 

94 """ 

95 npoints = len(self._xList) 

96 

97 if npoints < 1: 

98 raise RuntimeError("Cannot create scaling image. Found no fluxMag0s to interpolate") 

99 

100 image = afwImage.ImageF(bbox, numpy.mean(self._scaleList)) 

101 

102 return image 

103 

104 

105class ScaleZeroPointConfig(pexConfig.Config): 

106 """Config for ScaleZeroPointTask 

107 """ 

108 zeroPoint = pexConfig.Field( 

109 dtype=float, 

110 doc="desired photometric zero point", 

111 default=27.0, 

112 ) 

113 

114 

115class SpatialScaleZeroPointConfig(ScaleZeroPointConfig): 

116 selectFluxMag0 = pexConfig.ConfigurableField( 

117 doc="Task to select data to compute spatially varying photometric zeropoint", 

118 target=BaseSelectImagesTask, 

119 ) 

120 

121 interpStyle = pexConfig.ChoiceField( 

122 dtype=str, 

123 doc="Algorithm to interpolate the flux scalings;" 

124 "Currently only one choice implemented", 

125 default="CONSTANT", 

126 allowed={ 

127 "CONSTANT": "Use a single constant value", 

128 } 

129 ) 

130 

131 

132class ScaleZeroPointTask(pipeBase.Task): 

133 """Compute scale factor to scale exposures to a desired photometric zero point 

134 

135 This simple version assumes that the zero point is spatially invariant. 

136 """ 

137 ConfigClass = ScaleZeroPointConfig 

138 _DefaultName = "scaleZeroPoint" 

139 

140 def __init__(self, *args, **kwargs): 

141 """Construct a ScaleZeroPointTask 

142 """ 

143 pipeBase.Task.__init__(self, *args, **kwargs) 

144 

145 # flux at mag=0 is 10^(zeroPoint/2.5) because m = -2.5*log10(F/F0) 

146 fluxMag0 = 10**(0.4 * self.config.zeroPoint) 

147 self._photoCalib = afwImage.makePhotoCalibFromCalibZeroPoint(fluxMag0, 0.0) 

148 

149 def run(self, exposure, dataRef=None): 

150 """Scale the specified exposure to the desired photometric zeropoint 

151 

152 @param[in,out] exposure: exposure to scale; masked image is scaled in place 

153 @param[in] dataRef: dataRef for exposure. 

154 Not used, but in API so that users can switch between spatially variant 

155 and invariant tasks 

156 @return a pipeBase.Struct containing: 

157 - imageScaler: the image scaling object used to scale exposure 

158 """ 

159 imageScaler = self.computeImageScaler(exposure=exposure, dataRef=dataRef) 

160 mi = exposure.getMaskedImage() 

161 imageScaler.scaleMaskedImage(mi) 

162 return pipeBase.Struct( 

163 imageScaler=imageScaler, 

164 ) 

165 

166 def computeImageScaler(self, exposure, dataRef=None): 

167 """Compute image scaling object for a given exposure. 

168 

169 @param[in] exposure: exposure for which scaling is desired 

170 @param[in] dataRef: dataRef for exposure. 

171 Not used, but in API so that users can switch between spatially variant 

172 and invariant tasks 

173 """ 

174 scale = self.scaleFromPhotoCalib(exposure.getPhotoCalib()).scale 

175 return ImageScaler(scale) 

176 

177 def getPhotoCalib(self): 

178 """Get desired PhotoCalib 

179 

180 @return calibration (lsst.afw.image.PhotoCalib) with fluxMag0 set appropriately for config.zeroPoint 

181 """ 

182 return self._photoCalib 

183 

184 def scaleFromPhotoCalib(self, calib): 

185 """Compute the scale for the specified PhotoCalib 

186 

187 Compute scale, such that if pixelCalib describes the photometric zeropoint of a pixel 

188 then the following scales that pixel to the photometric zeropoint specified by config.zeroPoint: 

189 scale = computeScale(pixelCalib) 

190 pixel *= scale 

191 

192 @return a pipeBase.Struct containing: 

193 - scale, as described above. 

194 

195 @note: returns a struct to leave room for scaleErr in a future implementation. 

196 """ 

197 fluxAtZeroPoint = calib.magnitudeToInstFlux(self.config.zeroPoint) 

198 return pipeBase.Struct( 

199 scale=1.0 / fluxAtZeroPoint, 

200 ) 

201 

202 def scaleFromFluxMag0(self, fluxMag0): 

203 """Compute the scale for the specified fluxMag0 

204 

205 This is a wrapper around scaleFromPhotoCalib, which see for more information 

206 

207 @param[in] fluxMag0 

208 @return a pipeBase.Struct containing: 

209 - scale, as described in scaleFromPhotoCalib. 

210 """ 

211 calib = afwImage.makePhotoCalibFromCalibZeroPoint(fluxMag0, 0.0) 

212 return self.scaleFromPhotoCalib(calib) 

213 

214 

215class SpatialScaleZeroPointTask(ScaleZeroPointTask): 

216 """Compute spatially varying scale factor to scale exposures to a desired photometric zero point 

217 """ 

218 ConfigClass = SpatialScaleZeroPointConfig 

219 _DefaultName = "scaleZeroPoint" 

220 

221 def __init__(self, *args, **kwargs): 

222 ScaleZeroPointTask.__init__(self, *args, **kwargs) 

223 self.makeSubtask("selectFluxMag0") 

224 

225 def run(self, exposure, dataRef): 

226 """Scale the specified exposure to the desired photometric zeropoint 

227 

228 @param[in,out] exposure: exposure to scale; masked image is scaled in place 

229 @param[in] dataRef: dataRef for exposure 

230 

231 @return a pipeBase.Struct containing: 

232 - imageScaler: the image scaling object used to scale exposure 

233 """ 

234 imageScaler = self.computeImageScaler(exposure=exposure, dataRef=dataRef) 

235 mi = exposure.getMaskedImage() 

236 imageScaler.scaleMaskedImage(mi) 

237 return pipeBase.Struct( 

238 imageScaler=imageScaler, 

239 ) 

240 

241 def computeImageScaler(self, exposure, dataRef): 

242 """Compute image scaling object for a given exposure. 

243 

244 @param[in] exposure: exposure for which scaling is desired. Only wcs and bbox are used. 

245 @param[in] dataRef: dataRef of exposure 

246 dataRef.dataId used to retrieve all applicable fluxMag0's from a database. 

247 @return a SpatialImageScaler 

248 """ 

249 

250 wcs = exposure.getWcs() 

251 

252 fluxMagInfoList = self.selectFluxMag0.run(dataRef.dataId).fluxMagInfoList 

253 

254 xList = [] 

255 yList = [] 

256 scaleList = [] 

257 

258 for fluxMagInfo in fluxMagInfoList: 

259 # find center of field in tract coordinates 

260 if not fluxMagInfo.coordList: 

261 raise RuntimeError("no x,y data for fluxMagInfo") 

262 ctr = geom.Extent2D() 

263 for coord in fluxMagInfo.coordList: 

264 # accumulate x, y 

265 ctr += geom.Extent2D(wcs.skyToPixel(coord)) 

266 # and find average x, y as the center of the chip 

267 ctr = geom.Point2D(ctr / len(fluxMagInfo.coordList)) 

268 xList.append(ctr.getX()) 

269 yList.append(ctr.getY()) 

270 scaleList.append(self.scaleFromFluxMag0(fluxMagInfo.fluxMag0).scale) 

271 

272 self.log.info("Found %d flux scales for interpolation: %s", 

273 len(scaleList), [f"{s:%0.4f}" for s in scaleList]) 

274 return SpatialImageScaler( 

275 interpStyle=self.config.interpStyle, 

276 xList=xList, 

277 yList=yList, 

278 scaleList=scaleList, 

279 )