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