LSST Applications g06d8191974+de063e15a7,g180d380827+d0b6459378,g2079a07aa2+86d27d4dc4,g2305ad1205+f1ae3263cc,g29320951ab+5752d78b6e,g2bbee38e9b+85cf0a37e7,g337abbeb29+85cf0a37e7,g33d1c0ed96+85cf0a37e7,g3a166c0a6a+85cf0a37e7,g3ddfee87b4+b5254b9343,g48712c4677+9ea88d309d,g487adcacf7+05f7dba17f,g50ff169b8f+96c6868917,g52b1c1532d+585e252eca,g591dd9f2cf+48904e3942,g64a986408d+de063e15a7,g858d7b2824+de063e15a7,g864b0138d7+33ab2bc355,g8a8a8dda67+585e252eca,g99cad8db69+4508353287,g9c22b2923f+53520f316c,g9ddcbc5298+9a081db1e4,ga1e77700b3+15fc3df1f7,gb0e22166c9+60f28cb32d,gba4ed39666+c2a2e4ac27,gbb8dafda3b+ccb7f83a87,gc120e1dc64+6caf640b9b,gc28159a63d+85cf0a37e7,gc3e9b769f7+548c5e05a3,gcf0d15dbbd+b5254b9343,gdaeeff99f8+f9a426f77a,ge6526c86ff+515b6c9330,ge79ae78c31+85cf0a37e7,gee10cc3b42+585e252eca,gff1a9f87cc+de063e15a7,w.2024.17
LSST Data Management Base Package
Loading...
Searching...
No Matches
colorterms.py
Go to the documentation of this file.
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/>.
21
22__all__ = ["ColortermNotFoundError", "Colorterm", "ColortermDict", "ColortermLibrary"]
23
24import fnmatch
25import warnings
26
27import numpy as np
28import astropy.units as u
29
30from lsst.afw.image import abMagErrFromFluxErr
31from lsst.pex.config import Config, Field, ConfigDictField
32
33
34class ColortermNotFoundError(LookupError):
35 """Exception class indicating we couldn't find a colorterm
36 """
37 pass
38
39
41 """Colorterm correction for one pair of filters
42
43 Notes
44 -----
45 The transformed magnitude p' is given by:
46 p' = primary + c0 + c1*(primary - secondary) + c2*(primary - secondary)**2
47
48 To construct a Colorterm, use keyword arguments:
49 Colorterm(primary=primaryFilterName, secondary=secondaryFilterName, c0=c0value, c1=c1Coeff, c2=c2Coeff)
50 where c0-c2 are optional. For example (omitting c2):
51 Colorterm(primary="g", secondary="r", c0=-0.00816446, c1=-0.08366937)
52
53 This is subclass of Config. That is a bit of a hack to make it easy to store the data
54 in an appropriate obs_* package as a config override file. In the long term some other
55 means of persistence will be used, at which point the constructor can be simplified
56 to not require keyword arguments. (Fixing DM-2831 will also allow making a custom constructor).
57 """
58 primary = Field(dtype=str, doc="name of primary filter")
59 secondary = Field(dtype=str, doc="name of secondary filter")
60 c0 = Field(dtype=float, default=0.0, doc="Constant parameter")
61 c1 = Field(dtype=float, default=0.0, doc="First-order parameter")
62 c2 = Field(dtype=float, default=0.0, doc="Second-order parameter")
63
64 def getCorrectedMagnitudes(self, refCat, filterName="deprecatedArgument"):
65 """Return the colorterm corrected magnitudes for a given filter.
66
67 Parameters
68 ----------
69 refCat : `lsst.afw.table.SimpleCatalog`
70 The reference catalog to apply color corrections to.
71 filterName : `str`, deprecated
72 The camera filter to correct the reference catalog into.
73 The ``filterName`` argument is unused and will be removed in v23.
74
75 Returns
76 -------
77 refMag : `np.ndarray`
78 The corrected AB magnitudes.
79 refMagErr : `np.ndarray`
80 The corrected AB magnitude errors.
81
82 Raises
83 ------
84 KeyError
85 Raised if the reference catalog does not have a flux uncertainty
86 for that filter.
87
88 Notes
89 -----
90 WARNING: I do not know that we can trust the propagation of magnitude
91 errors returned by this method. They need more thorough tests.
92 """
93 if filterName != "deprecatedArgument":
94 msg = "Colorterm.getCorrectedMagnitudes() `filterName` arg is unused and will be removed in v23."
95 warnings.warn(msg, category=FutureWarning, stacklevel=2)
96
97 def getFluxes(fluxField):
98 """Get the flux and fluxErr of this field from refCat.
99
100 Parameters
101 ----------
102 fluxField : `str`
103 Name of the source flux field to use.
104
105 Returns
106 -------
107 refFlux : `Unknown`
108 refFluxErr : `Unknown`
109
110 Raises
111 ------
112 KeyError
113 Raised if reference catalog does not have flux uncertainties for the given flux field.
114 """
115 fluxKey = refCat.schema.find(fluxField).key
116 refFlux = refCat[fluxKey]
117 try:
118 fluxErrKey = refCat.schema.find(fluxField + "Err").key
119 refFluxErr = refCat[fluxErrKey]
120 except KeyError as e:
121 raise KeyError("Reference catalog does not have flux uncertainties for %s" % fluxField) from e
122
123 return refFlux, refFluxErr
124
125 primaryFlux, primaryErr = getFluxes(self.primary + "_flux")
126 secondaryFlux, secondaryErr = getFluxes(self.secondary + "_flux")
127
128 primaryMag = u.Quantity(primaryFlux, u.nJy).to_value(u.ABmag)
129 secondaryMag = u.Quantity(secondaryFlux, u.nJy).to_value(u.ABmag)
130
131 refMag = self.transformMags(primaryMag, secondaryMag)
132 refFluxErrArr = self.propagateFluxErrors(primaryErr, secondaryErr)
133
134 # HACK convert to Jy until we have a replacement for this (DM-16903)
135 refMagErr = abMagErrFromFluxErr(refFluxErrArr*1e-9, primaryFlux*1e-9)
136
137 return refMag, refMagErr
138
139 def transformSource(self, source):
140 """Transform the brightness of a source
141
142 Parameters
143 ----------
144 source : `Unknown`
145 Source whose brightness is to be converted; must support get(filterName)
146 (e.g. source.get("r")) method, as do afw::table::Source and dicts.
147
148 Returns
149 -------
150 transformed : `float`
151 The transformed source magnitude.
152 """
153 return self.transformMags(source.get(self.primary), source.get(self.secondary))
154
155 def transformMags(self, primary, secondary):
156 """Transform brightness
157
158 Parameters
159 ----------
160 primary : `float`
161 Brightness in primary filter (magnitude).
162 secondary : `float`
163 Brightness in secondary filter (magnitude).
164
165 Returns
166 -------
167 transformed : `float`
168 The transformed brightness (as a magnitude).
169 """
170 color = primary - secondary
171 return primary + self.c0 + color*(self.c1 + color*self.c2)
172
173 def propagateFluxErrors(self, primaryFluxErr, secondaryFluxErr):
174 return np.hypot((1 + self.c1)*primaryFluxErr, self.c1*secondaryFluxErr)
175
176
178 """A mapping of physical filter label to Colorterm
179
180 Different reference catalogs may need different ColortermDicts; see ColortermLibrary
181
182 To construct a ColortermDict use keyword arguments:
183 ColortermDict(data=dataDict)
184 where dataDict is a Python dict of filterName: Colorterm
185 For example:
186 ColortermDict(data={
187 'g': Colorterm(primary="g", secondary="r", c0=-0.00816446, c1=-0.08366937, c2=-0.00726883),
188 'r': Colorterm(primary="r", secondary="i", c0= 0.00231810, c1= 0.01284177, c2=-0.03068248),
189 'i': Colorterm(primary="i", secondary="z", c0= 0.00130204, c1=-0.16922042, c2=-0.01374245),
190 })
191 The constructor will likely be simplified at some point.
192
193 This is subclass of Config. That is a bit of a hack to make it easy to store the data
194 in an appropriate obs_* package as a config override file. In the long term some other
195 means of persistence will be used, at which point the constructor can be made saner.
196 """
198 doc="Mapping of filter name to Colorterm",
199 keytype=str,
200 itemtype=Colorterm,
201 default={},
202 )
203
204
206 """A mapping of photometric reference catalog name or glob to ColortermDict
207
208 Notes
209 -----
210 This allows photometric calibration using a variety of reference catalogs.
211
212 To construct a ColortermLibrary, use keyword arguments:
213 ColortermLibrary(data=dataDict)
214 where dataDict is a Python dict of catalog_name_or_glob: ColortermDict
215
216 Examples
217 --------
218
219 .. code-block:: none
220
221 ColortermLibrary(data = {
222 "hsc": ColortermDict(data={
223 'g': Colorterm(primary="g", secondary="g"),
224 'r': Colorterm(primary="r", secondary="r"),
225 ...
226 }),
227 "sdss": ColortermDict(data={
228 'g': Colorterm(primary="g",
229 secondary="r",
230 c0=-0.00816446,
231 c1=-0.08366937,
232 c2=-0.00726883),
233 'r': Colorterm(primary="r",
234 secondary="i",
235 c0= 0.00231810,
236 c1= 0.01284177,
237 c2=-0.03068248),
238 ...
239 }),
240 })
241
242 This is subclass of Config. That is a bit of a hack to make it easy to store the data
243 in an appropriate obs package as a config override file. In the long term some other
244 means of persistence will be used, at which point the constructor can be made saner.
245 """
247 doc="Mapping of reference catalog name (or glob) to ColortermDict",
248 keytype=str,
249 itemtype=ColortermDict,
250 default={},
251 )
252
253 def getColorterm(self, physicalFilter, photoCatName, doRaise=True):
254 """Get the appropriate Colorterm from the library
255
256 Use dict of color terms in the library that matches the photoCatName.
257 If the photoCatName exactly matches an entry in the library, that
258 dict is used; otherwise if the photoCatName matches a single glob (shell syntax,
259 e.g., "sdss-*" will match "sdss-dr8"), then that is used. If there is no
260 exact match and no unique match to the globs, raise an exception.
261
262 Parameters
263 ----------
264 physicalFilter : `str`
265 Label of physical filter to correct to.
266 photoCatName : `str`
267 Name of photometric reference catalog from which to retrieve the data.
268 This argument is not glob-expanded (but the catalog names in the library are,
269 if no exact match is found).
270 doRaise : `bool`
271 If True then raise ColortermNotFoundError if no suitable Colorterm found;
272 If False then return a null Colorterm with physicalFilter as the primary and secondary filter.
273
274 Returns
275 -------
276 ctDict : `Unknown`
277 The appropriate Colorterm.
278
279 Raises
280 ------
281 ColortermNotFoundError
282 If no suitable Colorterm found and doRaise true;
283 other exceptions may be raised for unexpected errors, regardless of the value of doRaise.
284 """
285 try:
286 trueRefCatName = None
287 ctDictConfig = self.data.get(photoCatName)
288 if ctDictConfig is None:
289 # try glob expression
290 matchList = [libRefNameGlob for libRefNameGlob in self.data
291 if fnmatch.fnmatch(photoCatName, libRefNameGlob)]
292 if len(matchList) == 1:
293 trueRefCatName = matchList[0]
294 ctDictConfig = self.data[trueRefCatName]
295 elif len(matchList) > 1:
297 "Multiple library globs match photoCatName %r: %s" % (photoCatName, matchList))
298 else:
300 "No colorterm dict found with photoCatName %r" % photoCatName)
301 ctDict = ctDictConfig.data
302 if physicalFilter not in ctDict:
303 errMsg = "No colorterm found for filter %r with photoCatName %r" % (
304 physicalFilter, photoCatName)
305 if trueRefCatName is not None:
306 errMsg += " = catalog %r" % (trueRefCatName,)
307 raise ColortermNotFoundError(errMsg)
308 return ctDict[physicalFilter]
309 except ColortermNotFoundError:
310 if doRaise:
311 raise
312 else:
313 return Colorterm(physicalFilter, physicalFilter)
propagateFluxErrors(self, primaryFluxErr, secondaryFluxErr)
getCorrectedMagnitudes(self, refCat, filterName="deprecatedArgument")
Definition colorterms.py:64
transformMags(self, primary, secondary)
getColorterm(self, physicalFilter, photoCatName, doRaise=True)