lsst.pipe.tasks  20.0.0-39-gbdec61e6+2040cb0b94
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 import lsst.pex.exceptions as pexExcept
29 from lsst.pex.config import Config, Field, ConfigDictField
30 from lsst.afw.image import Filter
31 
32 __all__ = ["ColortermNotFoundError", "Colorterm", "ColortermDict", "ColortermLibrary"]
33 
34 
35 class ColortermNotFoundError(LookupError):
36  """Exception class indicating we couldn't find a colorterm
37  """
38  pass
39 
40 
41 class 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 
141 class 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 
169 class 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:
235  "Multiple library globs match photoCatName %r: %s" % (photoCatName, matchList))
236  else:
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)
lsst.pipe.tasks.colorterms.Colorterm.c0
c0
Definition: colorterms.py:59
lsst::afw::image
lsst.pipe.tasks.colorterms.Colorterm.propagateFluxErrors
def propagateFluxErrors(self, primaryFluxErr, secondaryFluxErr)
Definition: colorterms.py:137
lsst.pipe.tasks.colorterms.Colorterm.secondary
secondary
Definition: colorterms.py:58
lsst::afw::image::Filter
lsst.pipe.tasks.colorterms.ColortermLibrary
A mapping of photometric reference catalog name or glob to ColortermDict.
Definition: colorterms.py:169
lsst.pipe.tasks.colorterms.Colorterm
Colorterm correction for one pair of filters.
Definition: colorterms.py:41
lsst.pipe.tasks.colorterms.ColortermLibrary.getColorterm
def getColorterm(self, filterName, photoCatName, doRaise=True)
Get the appropriate Colorterm from the library.
Definition: colorterms.py:203
lsst.pipe.tasks.colorterms.Colorterm.transformMags
def transformMags(self, primary, secondary)
Transform brightness.
Definition: colorterms.py:127
lsst.pipe.tasks.colorterms.Colorterm.c1
c1
Definition: colorterms.py:60
lsst.pipe.tasks.colorterms.Colorterm.primary
primary
Definition: colorterms.py:57
lsst::pex::config
lsst.pipe.tasks.colorterms.ColortermLibrary.data
data
Definition: colorterms.py:196
lsst.pipe.tasks.colorterms.ColortermNotFoundError
Definition: colorterms.py:35
lsst.pipe.tasks.colorterms.Colorterm.transformSource
def transformSource(self, source)
Transform the brightness of a source.
Definition: colorterms.py:118
lsst.pipe.tasks.colorterms.Colorterm.c2
c2
Definition: colorterms.py:61
lsst.pipe.tasks.colorterms.Colorterm.getCorrectedMagnitudes
def getCorrectedMagnitudes(self, refCat, filterName)
Definition: colorterms.py:63
lsst::pex::exceptions
lsst.pipe.tasks.colorterms.ColortermDict
A mapping of filterName to Colorterm.
Definition: colorterms.py:141