lsst.pipe.tasks  13.0-66-gfbf2f2ce+5
scaleZeroPoint.py
Go to the documentation of this file.
1 #
2 # LSST Data Management System
3 # Copyright 2008, 2009, 2010, 2011, 2012 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 from __future__ import absolute_import, division, print_function
23 from builtins import object
24 import numpy
25 import lsst.afw.geom as afwGeom
26 import lsst.afw.image as afwImage
27 import lsst.pex.config as pexConfig
28 import lsst.pipe.base as pipeBase
29 from lsst.pipe.tasks.selectImages import BaseSelectImagesTask
30 
31 __all__ = ["ImageScaler", "SpatialImageScaler", "ScaleZeroPointTask"]
32 
33 
34 class ImageScaler(object):
35  """A class that scales an image
36 
37  This version uses a single scalar. Fancier versions may use a spatially varying scale.
38  """
39 
40  def __init__(self, scale=1.0):
41  """Construct an ImageScaler
42 
43  @param[in] scale: scale correction to apply (see scaleMaskedImage);
44  """
45  self._scale = scale
46 
47  def scaleMaskedImage(self, maskedImage):
48  """Scale the specified image or masked image in place.
49 
50  @param[in,out] maskedImage: masked image to scale
51  """
52  maskedImage *= self._scale
53 
54 
56  """Multiplicative image scaler using interpolation over a grid of points.
57 
58  Contains the x, y positions in tract coordinates and the scale factors.
59  Interpolates only when scaleMaskedImage() or getInterpImage() is called.
60 
61  Currently the only type of 'interpolation' implemented is CONSTANT which calculates the mean.
62  """
63 
64  def __init__(self, interpStyle, xList, yList, scaleList):
65  """Constructor
66 
67  @param[in] interpStyle: interpolation style (CONSTANT is only option)
68  @param[in] xList: list of X pixel positions
69  @param[in] yList: list of Y pixel positions
70  @param[in] scaleList: list of multiplicative scale factors at (x,y)
71 
72  @raise RuntimeError if the lists have different lengths
73  """
74  if len(xList) != len(yList) or len(xList) != len(scaleList):
75  raise RuntimeError(
76  "len(xList)=%s len(yList)=%s, len(scaleList)=%s but all lists must have the same length" %
77  (len(xList), len(yList), len(scaleList)))
78 
79  # Eventually want this do be: self.interpStyle = getattr(afwMath.Interpolate2D, interpStyle)
80  self._xList = xList
81  self._yList = yList
82  self._scaleList = scaleList
83 
84  def scaleMaskedImage(self, maskedImage):
85  """Apply scale correction to the specified masked image
86 
87  @param[in,out] image to scale; scale is applied in place
88  """
89  scale = self.getInterpImage(maskedImage.getBBox())
90  maskedImage *= scale
91 
92  def getInterpImage(self, bbox):
93  """Return an image containing the scale correction with same bounding box as supplied.
94 
95  @param[in] bbox: integer bounding box for image (afwGeom.Box2I)
96  """
97  npoints = len(self._xList)
98 
99  if npoints < 1:
100  raise RuntimeError("Cannot create scaling image. Found no fluxMag0s to interpolate")
101 
102  image = afwImage.ImageF(bbox, numpy.mean(self._scaleList))
103 
104  return image
105 
106 
107 class ScaleZeroPointConfig(pexConfig.Config):
108  """Config for ScaleZeroPointTask
109  """
110  zeroPoint = pexConfig.Field(
111  dtype=float,
112  doc="desired photometric zero point",
113  default=27.0,
114  )
115 
116 
118  selectFluxMag0 = pexConfig.ConfigurableField(
119  doc="Task to select data to compute spatially varying photometric zeropoint",
120  target=BaseSelectImagesTask,
121  )
122 
123  interpStyle = pexConfig.ChoiceField(
124  dtype=str,
125  doc="Algorithm to interpolate the flux scalings;"
126  "Currently only one choice implemented",
127  default="CONSTANT",
128  allowed={
129  "CONSTANT": "Use a single constant value",
130  }
131  )
132 
133 
134 class ScaleZeroPointTask(pipeBase.Task):
135  """Compute scale factor to scale exposures to a desired photometric zero point
136 
137  This simple version assumes that the zero point is spatially invariant.
138  """
139  ConfigClass = ScaleZeroPointConfig
140  _DefaultName = "scaleZeroPoint"
141 
142  def __init__(self, *args, **kwargs):
143  """Construct a ScaleZeroPointTask
144  """
145  pipeBase.Task.__init__(self, *args, **kwargs)
146 
147  # flux at mag=0 is 10^(zeroPoint/2.5) because m = -2.5*log10(F/F0)
148  fluxMag0 = 10**(0.4 * self.config.zeroPoint)
149  self._calib = afwImage.Calib()
150  self._calib.setFluxMag0(fluxMag0)
151 
152  def run(self, exposure, dataRef=None):
153  """Scale the specified exposure to the desired photometric zeropoint
154 
155  @param[in,out] exposure: exposure to scale; masked image is scaled in place
156  @param[in] dataRef: dataRef for exposure.
157  Not used, but in API so that users can switch between spatially variant
158  and invariant tasks
159  @return a pipeBase.Struct containing:
160  - imageScaler: the image scaling object used to scale exposure
161  """
162  imageScaler = self.computeImageScaler(exposure=exposure, dataRef=dataRef)
163  mi = exposure.getMaskedImage()
164  imageScaler.scaleMaskedImage(mi)
165  return pipeBase.Struct(
166  imageScaler=imageScaler,
167  )
168 
169  def computeImageScaler(self, exposure, dataRef=None):
170  """Compute image scaling object for a given exposure.
171 
172  @param[in] exposure: exposure for which scaling is desired
173  @param[in] dataRef: dataRef for exposure.
174  Not used, but in API so that users can switch between spatially variant
175  and invariant tasks
176  """
177  scale = self.scaleFromCalib(exposure.getCalib()).scale
178  return ImageScaler(scale)
179 
180  def getCalib(self):
181  """Get desired Calib
182 
183  @return calibration (lsst.afw.image.Calib) with fluxMag0 set appropriately for config.zeroPoint
184  """
185  return self._calib
186 
187  def scaleFromCalib(self, calib):
188  """Compute the scale for the specified Calib
189 
190  Compute scale, such that if pixelCalib describes the photometric zeropoint of a pixel
191  then the following scales that pixel to the photometric zeropoint specified by config.zeroPoint:
192  scale = computeScale(pixelCalib)
193  pixel *= scale
194 
195  @return a pipeBase.Struct containing:
196  - scale, as described above.
197 
198  @note: returns a struct to leave room for scaleErr in a future implementation.
199  """
200  fluxAtZeroPoint = calib.getFlux(self.config.zeroPoint)
201  return pipeBase.Struct(
202  scale=1.0 / fluxAtZeroPoint,
203  )
204 
205  def scaleFromFluxMag0(self, fluxMag0):
206  """Compute the scale for the specified fluxMag0
207 
208  This is a wrapper around scaleFromCalib, which see for more information
209 
210  @param[in] fluxMag0
211  @return a pipeBase.Struct containing:
212  - scale, as described in scaleFromCalib.
213  """
214  calib = afwImage.Calib()
215  calib.setFluxMag0(fluxMag0)
216  return self.scaleFromCalib(calib)
217 
218 
220  """Compute spatially varying scale factor to scale exposures to a desired photometric zero point
221  """
222  ConfigClass = SpatialScaleZeroPointConfig
223  _DefaultName = "scaleZeroPoint"
224 
225  def __init__(self, *args, **kwargs):
226  ScaleZeroPointTask.__init__(self, *args, **kwargs)
227  self.makeSubtask("selectFluxMag0")
228 
229  def run(self, exposure, dataRef):
230  """Scale the specified exposure to the desired photometric zeropoint
231 
232  @param[in,out] exposure: exposure to scale; masked image is scaled in place
233  @param[in] dataRef: dataRef for exposure
234 
235  @return a pipeBase.Struct containing:
236  - imageScaler: the image scaling object used to scale exposure
237  """
238  imageScaler = self.computeImageScaler(exposure=exposure, dataRef=dataRef)
239  mi = exposure.getMaskedImage()
240  imageScaler.scaleMaskedImage(mi)
241  return pipeBase.Struct(
242  imageScaler=imageScaler,
243  )
244 
245  def computeImageScaler(self, exposure, dataRef):
246  """Compute image scaling object for a given exposure.
247 
248  @param[in] exposure: exposure for which scaling is desired. Only wcs and bbox are used.
249  @param[in] dataRef: dataRef of exposure
250  dataRef.dataId used to retrieve all applicable fluxMag0's from a database.
251  @return a SpatialImageScaler
252  """
253 
254  wcs = exposure.getWcs()
255 
256  fluxMagInfoList = self.selectFluxMag0.run(dataRef.dataId).fluxMagInfoList
257 
258  xList = []
259  yList = []
260  scaleList = []
261 
262  for fluxMagInfo in fluxMagInfoList:
263  # find center of field in tract coordinates
264  if not fluxMagInfo.coordList:
265  raise RuntimeError("no x,y data for fluxMagInfo")
266  ctr = afwGeom.Extent2D()
267  for coord in fluxMagInfo.coordList:
268  # accumulate x, y
269  ctr += afwGeom.Extent2D(wcs.skyToPixel(coord))
270  # and find average x, y as the center of the chip
271  ctr = afwGeom.Point2D(ctr / len(fluxMagInfo.coordList))
272  xList.append(ctr.getX())
273  yList.append(ctr.getY())
274  scaleList.append(self.scaleFromFluxMag0(fluxMagInfo.fluxMag0).scale)
275 
276  self.log.info("Found %d flux scales for interpolation: %s" % (len(scaleList),
277  ["%0.4f"%(s) for s in scaleList]))
278  return SpatialImageScaler(
279  interpStyle=self.config.interpStyle,
280  xList=xList,
281  yList=yList,
282  scaleList=scaleList,
283  )
def computeImageScaler(self, exposure, dataRef=None)
def __init__(self, interpStyle, xList, yList, scaleList)