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 

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

29 

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

31 

32 

33class ColortermNotFoundError(LookupError): 

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

35 """ 

36 pass 

37 

38 

39class Colorterm(Config): 

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

41 

42 The transformed magnitude p' is given by 

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

44 

45 To construct a Colorterm, use keyword arguments: 

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

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

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

49 

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

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

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

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

54 """ 

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

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

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

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

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

60 

61 def getCorrectedMagnitudes(self, refCat, filterName): 

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

63 

64 Parameters 

65 ---------- 

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

67 The reference catalog to apply color corrections to. 

68 filterName : `str` 

69 The camera filter to correct the reference catalog into. 

70 

71 Returns 

72 ------- 

73 RefMag : `np.ndarray` 

74 The corrected AB magnitudes. 

75 RefMagErr : `np.ndarray` 

76 The corrected AB magnitude errors. 

77 

78 Raises 

79 ------ 

80 KeyError 

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

82 for that filter. 

83 

84 Notes 

85 ----- 

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

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

88 """ 

89 

90 def getFluxes(fluxField): 

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

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

93 refFlux = refCat[fluxKey] 

94 try: 

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

96 refFluxErr = refCat[fluxErrKey] 

97 except KeyError as e: 

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

99 

100 return refFlux, refFluxErr 

101 

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

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

104 

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

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

107 

108 refMag = self.transformMags(primaryMag, secondaryMag) 

109 refFluxErrArr = self.propagateFluxErrors(primaryErr, secondaryErr) 

110 

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

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

113 

114 return refMag, refMagErr 

115 

116 def transformSource(self, source): 

117 """!Transform the brightness of a source 

118 

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

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

121 @return the transformed source magnitude 

122 """ 

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

124 

125 def transformMags(self, primary, secondary): 

126 """!Transform brightness 

127 

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

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

130 @return the transformed brightness (as a magnitude) 

131 """ 

132 color = primary - secondary 

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

134 

135 def propagateFluxErrors(self, primaryFluxErr, secondaryFluxErr): 

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

137 

138 

139class ColortermDict(Config): 

140 """!A mapping of filterName to Colorterm 

141 

142 Different reference catalogs may need different ColortermDicts; see ColortermLibrary 

143 

144 To construct a ColortermDict use keyword arguments: 

145 ColortermDict(data=dataDict) 

146 where dataDict is a Python dict of filterName: Colorterm 

147 For example: 

148 ColortermDict(data={ 

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

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

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

152 }) 

153 The constructor will likely be simplified at some point. 

154 

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

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

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

158 """ 

159 data = ConfigDictField( 

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

161 keytype=str, 

162 itemtype=Colorterm, 

163 default={}, 

164 ) 

165 

166 

167class ColortermLibrary(Config): 

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

169 

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

171 

172 To construct a ColortermLibrary, use keyword arguments: 

173 ColortermLibrary(data=dataDict) 

174 where dataDict is a Python dict of catalog_name_or_glob: ColortermDict 

175 

176 For example: 

177 ColortermLibrary(data = { 

178 "hsc*": ColortermDict(data={ 

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

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

181 ... 

182 }), 

183 "sdss*": ColortermDict(data={ 

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

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

186 ... 

187 }), 

188 }) 

189 

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

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

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

193 """ 

194 data = ConfigDictField( 

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

196 keytype=str, 

197 itemtype=ColortermDict, 

198 default={}, 

199 ) 

200 

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

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

203 

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

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

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

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

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

209 

210 @param filterName name of filter 

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

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

213 if no exact match is found). 

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

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

216 @return the appropriate Colorterm 

217 

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

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

220 """ 

221 try: 

222 trueRefCatName = None 

223 ctDictConfig = self.data.get(photoCatName) 

224 if ctDictConfig is None: 

225 # try glob expression 

226 matchList = [libRefNameGlob for libRefNameGlob in self.data 

227 if fnmatch.fnmatch(photoCatName, libRefNameGlob)] 

228 if len(matchList) == 1: 

229 trueRefCatName = matchList[0] 

230 ctDictConfig = self.data[trueRefCatName] 

231 elif len(matchList) > 1: 

232 raise ColortermNotFoundError( 

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

234 else: 

235 raise ColortermNotFoundError( 

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

237 ctDict = ctDictConfig.data 

238 if filterName not in ctDict: 

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

240 filterName, photoCatName) 

241 if trueRefCatName is not None: 

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

243 raise ColortermNotFoundError(errMsg) 

244 return ctDict[filterName] 

245 except ColortermNotFoundError: 

246 if doRaise: 

247 raise 

248 else: 

249 return Colorterm(filterName, filterName)