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# 

2# LSST Data Management System 

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

23 

24import numpy as np 

25import astropy.units as u 

26 

27from lsst.afw.image import abMagErrFromFluxErr 

28import lsst.pex.exceptions as pexExcept 

29from lsst.pex.config import Config, Field, ConfigDictField 

30from lsst.afw.image import Filter 

31 

32__all__ = ["ColortermNotFoundError", "Colorterm", "ColortermDict", "ColortermLibrary"] 

33 

34 

35class ColortermNotFoundError(LookupError): 

36 """Exception class indicating we couldn't find a colorterm 

37 """ 

38 pass 

39 

40 

41class Colorterm(Config): 

42 """!Colorterm correction for one pair of filters 

43 

44 The transformed magnitude p' is given by 

45 p' = primary + c0 + c1*(primary - secondary) + c2*(primary - secondary)**2 

46 

47 To construct a Colorterm, use keyword arguments: 

48 Colorterm(primary=primaryFilterName, secondary=secondaryFilterName, c0=c0value, c1=c1Coeff, c2=c2Coeff) 

49 where c0-c2 are optional. For example (omitting c2): 

50 Colorterm(primary="g", secondary="r", c0=-0.00816446, c1=-0.08366937) 

51 

52 This is subclass of Config. That is a bit of a hack to make it easy to store the data 

53 in an appropriate obs_* package as a config override file. In the long term some other 

54 means of persistence will be used, at which point the constructor can be simplified 

55 to not require keyword arguments. (Fixing DM-2831 will also allow making a custom constructor). 

56 """ 

57 primary = Field(dtype=str, doc="name of primary filter") 

58 secondary = Field(dtype=str, doc="name of secondary filter") 

59 c0 = Field(dtype=float, default=0.0, doc="Constant parameter") 

60 c1 = Field(dtype=float, default=0.0, doc="First-order parameter") 

61 c2 = Field(dtype=float, default=0.0, doc="Second-order parameter") 

62 

63 def getCorrectedMagnitudes(self, refCat, filterName): 

64 """Return the colorterm corrected magnitudes for a given filter. 

65 

66 Parameters 

67 ---------- 

68 refCat : `lsst.afw.table.SimpleCatalog` 

69 The reference catalog to apply color corrections to. 

70 filterName : `str` 

71 The camera filter to correct the reference catalog into. 

72 

73 Returns 

74 ------- 

75 RefMag : `np.ndarray` 

76 The corrected AB magnitudes. 

77 RefMagErr : `np.ndarray` 

78 The corrected AB magnitude errors. 

79 

80 Raises 

81 ------ 

82 KeyError 

83 Raised if the reference catalog does not have a flux uncertainty 

84 for that filter. 

85 

86 Notes 

87 ----- 

88 WARNING: I do not know that we can trust the propagation of magnitude 

89 errors returned by this method. They need more thorough tests. 

90 """ 

91 

92 def getFluxes(fluxField): 

93 """Get the flux and fluxErr of this field from refCat.""" 

94 fluxKey = refCat.schema.find(fluxField).key 

95 refFlux = refCat[fluxKey] 

96 try: 

97 fluxErrKey = refCat.schema.find(fluxField + "Err").key 

98 refFluxErr = refCat[fluxErrKey] 

99 except KeyError as e: 

100 raise KeyError("Reference catalog does not have flux uncertainties for %s" % fluxField) from e 

101 

102 return refFlux, refFluxErr 

103 

104 primaryFlux, primaryErr = getFluxes(self.primary + "_flux") 

105 secondaryFlux, secondaryErr = getFluxes(self.secondary + "_flux") 

106 

107 primaryMag = u.Quantity(primaryFlux, u.nJy).to_value(u.ABmag) 

108 secondaryMag = u.Quantity(secondaryFlux, u.nJy).to_value(u.ABmag) 

109 

110 refMag = self.transformMags(primaryMag, secondaryMag) 

111 refFluxErrArr = self.propagateFluxErrors(primaryErr, secondaryErr) 

112 

113 # HACK convert to Jy until we have a replacement for this (DM-16903) 

114 refMagErr = abMagErrFromFluxErr(refFluxErrArr*1e-9, primaryFlux*1e-9) 

115 

116 return refMag, refMagErr 

117 

118 def transformSource(self, source): 

119 """!Transform the brightness of a source 

120 

121 @param[in] source source whose brightness is to be converted; must support get(filterName) 

122 (e.g. source.get("r")) method, as do afw::table::Source and dicts. 

123 @return the transformed source magnitude 

124 """ 

125 return self.transformMags(source.get(self.primary), source.get(self.secondary)) 

126 

127 def transformMags(self, primary, secondary): 

128 """!Transform brightness 

129 

130 @param[in] primary brightness in primary filter (magnitude) 

131 @param[in] secondary brightness in secondary filter (magnitude) 

132 @return the transformed brightness (as a magnitude) 

133 """ 

134 color = primary - secondary 

135 return primary + self.c0 + color*(self.c1 + color*self.c2) 

136 

137 def propagateFluxErrors(self, primaryFluxErr, secondaryFluxErr): 

138 return np.hypot((1 + self.c1)*primaryFluxErr, self.c1*secondaryFluxErr) 

139 

140 

141class ColortermDict(Config): 

142 """!A mapping of filterName to Colorterm 

143 

144 Different reference catalogs may need different ColortermDicts; see ColortermLibrary 

145 

146 To construct a ColortermDict use keyword arguments: 

147 ColortermDict(data=dataDict) 

148 where dataDict is a Python dict of filterName: Colorterm 

149 For example: 

150 ColortermDict(data={ 

151 'g': Colorterm(primary="g", secondary="r", c0=-0.00816446, c1=-0.08366937, c2=-0.00726883), 

152 'r': Colorterm(primary="r", secondary="i", c0= 0.00231810, c1= 0.01284177, c2=-0.03068248), 

153 'i': Colorterm(primary="i", secondary="z", c0= 0.00130204, c1=-0.16922042, c2=-0.01374245), 

154 }) 

155 The constructor will likely be simplified at some point. 

156 

157 This is subclass of Config. That is a bit of a hack to make it easy to store the data 

158 in an appropriate obs_* package as a config override file. In the long term some other 

159 means of persistence will be used, at which point the constructor can be made saner. 

160 """ 

161 data = ConfigDictField( 

162 doc="Mapping of filter name to Colorterm", 

163 keytype=str, 

164 itemtype=Colorterm, 

165 default={}, 

166 ) 

167 

168 

169class ColortermLibrary(Config): 

170 """!A mapping of photometric reference catalog name or glob to ColortermDict 

171 

172 This allows photometric calibration using a variety of reference catalogs. 

173 

174 To construct a ColortermLibrary, use keyword arguments: 

175 ColortermLibrary(data=dataDict) 

176 where dataDict is a Python dict of catalog_name_or_glob: ColortermDict 

177 

178 For example: 

179 ColortermLibrary(data = { 

180 "hsc*": ColortermDict(data={ 

181 'g': Colorterm(primary="g", secondary="g"), 

182 'r': Colorterm(primary="r", secondary="r"), 

183 ... 

184 }), 

185 "sdss*": ColortermDict(data={ 

186 'g': Colorterm(primary="g", secondary="r", c0=-0.00816446, c1=-0.08366937, c2=-0.00726883), 

187 'r': Colorterm(primary="r", secondary="i", c0= 0.00231810, c1= 0.01284177, c2=-0.03068248), 

188 ... 

189 }), 

190 }) 

191 

192 This is subclass of Config. That is a bit of a hack to make it easy to store the data 

193 in an appropriate obs_* package as a config override file. In the long term some other 

194 means of persistence will be used, at which point the constructor can be made saner. 

195 """ 

196 data = ConfigDictField( 

197 doc="Mapping of reference catalog name (or glob) to ColortermDict", 

198 keytype=str, 

199 itemtype=ColortermDict, 

200 default={}, 

201 ) 

202 

203 def getColorterm(self, filterName, photoCatName, doRaise=True): 

204 """!Get the appropriate Colorterm from the library 

205 

206 Use dict of color terms in the library that matches the photoCatName. 

207 If the photoCatName exactly matches an entry in the library, that 

208 dict is used; otherwise if the photoCatName matches a single glob (shell syntax, 

209 e.g., "sdss-*" will match "sdss-dr8"), then that is used. If there is no 

210 exact match and no unique match to the globs, raise an exception. 

211 

212 @param filterName name of filter 

213 @param photoCatName name of photometric reference catalog from which to retrieve the data. 

214 This argument is not glob-expanded (but the catalog names in the library are, 

215 if no exact match is found). 

216 @param[in] doRaise if True then raise ColortermNotFoundError if no suitable Colorterm found; 

217 if False then return a null Colorterm with filterName as the primary and secondary filter 

218 @return the appropriate Colorterm 

219 

220 @throw ColortermNotFoundError if no suitable Colorterm found and doRaise true; 

221 other exceptions may be raised for unexpected errors, regardless of the value of doRaise 

222 """ 

223 try: 

224 trueRefCatName = None 

225 ctDictConfig = self.data.get(photoCatName) 

226 if ctDictConfig is None: 

227 # try glob expression 

228 matchList = [libRefNameGlob for libRefNameGlob in self.data 

229 if fnmatch.fnmatch(photoCatName, libRefNameGlob)] 

230 if len(matchList) == 1: 

231 trueRefCatName = matchList[0] 

232 ctDictConfig = self.data[trueRefCatName] 

233 elif len(matchList) > 1: 

234 raise ColortermNotFoundError( 

235 "Multiple library globs match photoCatName %r: %s" % (photoCatName, matchList)) 

236 else: 

237 raise ColortermNotFoundError( 

238 "No colorterm dict found with photoCatName %r" % photoCatName) 

239 ctDict = ctDictConfig.data 

240 if filterName not in ctDict: 

241 # Perhaps it's an alias 

242 try: 

243 filterName = Filter(Filter(filterName).getId()).getName() 

244 except pexExcept.NotFoundError: 

245 pass # this will be handled shortly 

246 if filterName not in ctDict: 

247 errMsg = "No colorterm found for filter %r with photoCatName %r" % ( 

248 filterName, photoCatName) 

249 if trueRefCatName is not None: 

250 errMsg += " = catalog %r" % (trueRefCatName,) 

251 raise ColortermNotFoundError(errMsg) 

252 return ctDict[filterName] 

253 except ColortermNotFoundError: 

254 if doRaise: 

255 raise 

256 else: 

257 return Colorterm(filterName, filterName)