lsst.pipe.tasks  21.0.0-41-g0a6ae792+3de7980432
colorterms.py
Go to the documentation of this file.
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 #
22 import fnmatch
23 
24 import numpy as np
25 import astropy.units as u
26 
27 from lsst.afw.image import abMagErrFromFluxErr
28 from lsst.pex.config import Config, Field, ConfigDictField
29 
30 __all__ = ["ColortermNotFoundError", "Colorterm", "ColortermDict", "ColortermLibrary"]
31 
32 
33 class ColortermNotFoundError(LookupError):
34  """Exception class indicating we couldn't find a colorterm
35  """
36  pass
37 
38 
39 class 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.primaryprimary + "_flux")
103  secondaryFlux, secondaryErr = getFluxes(self.secondarysecondary + "_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.transformMagstransformMags(primaryMag, secondaryMag)
109  refFluxErrArr = self.propagateFluxErrorspropagateFluxErrors(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.transformMagstransformMags(source.get(self.primaryprimary), source.get(self.secondarysecondary))
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.c0c0 + color*(self.c1c1 + color*self.c2c2)
134 
135  def propagateFluxErrors(self, primaryFluxErr, secondaryFluxErr):
136  return np.hypot((1 + self.c1c1)*primaryFluxErr, self.c1c1*secondaryFluxErr)
137 
138 
139 class 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 
167 class 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.datadata.get(photoCatName)
224  if ctDictConfig is None:
225  # try glob expression
226  matchList = [libRefNameGlob for libRefNameGlob in self.datadata
227  if fnmatch.fnmatch(photoCatName, libRefNameGlob)]
228  if len(matchList) == 1:
229  trueRefCatName = matchList[0]
230  ctDictConfig = self.datadata[trueRefCatName]
231  elif len(matchList) > 1:
233  "Multiple library globs match photoCatName %r: %s" % (photoCatName, matchList))
234  else:
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)
A mapping of filterName to Colorterm.
Definition: colorterms.py:139
Colorterm correction for one pair of filters.
Definition: colorterms.py:39
def propagateFluxErrors(self, primaryFluxErr, secondaryFluxErr)
Definition: colorterms.py:135
def getCorrectedMagnitudes(self, refCat, filterName)
Definition: colorterms.py:61
def transformMags(self, primary, secondary)
Transform brightness.
Definition: colorterms.py:125
def transformSource(self, source)
Transform the brightness of a source.
Definition: colorterms.py:116
A mapping of photometric reference catalog name or glob to ColortermDict.
Definition: colorterms.py:167
def getColorterm(self, filterName, photoCatName, doRaise=True)
Get the appropriate Colorterm from the library.
Definition: colorterms.py:201