LSST Applications  21.0.0-147-g0e635eb1+1acddb5be5,22.0.0+052faf71bd,22.0.0+1ea9a8b2b2,22.0.0+6312710a6c,22.0.0+729191ecac,22.0.0+7589c3a021,22.0.0+9f079a9461,22.0.1-1-g7d6de66+b8044ec9de,22.0.1-1-g87000a6+536b1ee016,22.0.1-1-g8e32f31+6312710a6c,22.0.1-10-gd060f87+016f7cdc03,22.0.1-12-g9c3108e+df145f6f68,22.0.1-16-g314fa6d+c825727ab8,22.0.1-19-g93a5c75+d23f2fb6d8,22.0.1-19-gb93eaa13+aab3ef7709,22.0.1-2-g8ef0a89+b8044ec9de,22.0.1-2-g92698f7+9f079a9461,22.0.1-2-ga9b0f51+052faf71bd,22.0.1-2-gac51dbf+052faf71bd,22.0.1-2-gb66926d+6312710a6c,22.0.1-2-gcb770ba+09e3807989,22.0.1-20-g32debb5+b8044ec9de,22.0.1-23-gc2439a9a+fb0756638e,22.0.1-3-g496fd5d+09117f784f,22.0.1-3-g59f966b+1e6ba2c031,22.0.1-3-g849a1b8+f8b568069f,22.0.1-3-gaaec9c0+c5c846a8b1,22.0.1-32-g5ddfab5d3+60ce4897b0,22.0.1-4-g037fbe1+64e601228d,22.0.1-4-g8623105+b8044ec9de,22.0.1-5-g096abc9+d18c45d440,22.0.1-5-g15c806e+57f5c03693,22.0.1-7-gba73697+57f5c03693,master-g6e05de7fdc+c1283a92b8,master-g72cdda8301+729191ecac,w.2021.39
LSST Data Management Base Package
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.geom as geom
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 
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 
38  def __init__(self, scale=1.0):
39  """Construct an ImageScaler
40 
41  @param[in] scale: scale correction to apply (see scaleMaskedImage);
42  """
43  self._scale_scale = scale
44 
45  def scaleMaskedImage(self, maskedImage):
46  """Scale the specified image or masked image in place.
47 
48  @param[in,out] maskedImage: masked image to scale
49  """
50  maskedImage *= self._scale_scale
51 
52 
54  """Multiplicative image scaler using interpolation over a grid of points.
55 
56  Contains the x, y positions in tract coordinates and the scale factors.
57  Interpolates only when scaleMaskedImage() or getInterpImage() is called.
58 
59  Currently the only type of 'interpolation' implemented is CONSTANT which calculates the mean.
60  """
61 
62  def __init__(self, interpStyle, xList, yList, scaleList):
63  """Constructor
64 
65  @param[in] interpStyle: interpolation style (CONSTANT is only option)
66  @param[in] xList: list of X pixel positions
67  @param[in] yList: list of Y pixel positions
68  @param[in] scaleList: list of multiplicative scale factors at (x,y)
69 
70  @raise RuntimeError if the lists have different lengths
71  """
72  if len(xList) != len(yList) or len(xList) != len(scaleList):
73  raise RuntimeError(
74  "len(xList)=%s len(yList)=%s, len(scaleList)=%s but all lists must have the same length" %
75  (len(xList), len(yList), len(scaleList)))
76 
77  # Eventually want this do be: self.interpStyle = getattr(afwMath.Interpolate2D, interpStyle)
78  self._xList_xList = xList
79  self._yList_yList = yList
80  self._scaleList_scaleList = scaleList
81 
82  def scaleMaskedImage(self, maskedImage):
83  """Apply scale correction to the specified masked image
84 
85  @param[in,out] image to scale; scale is applied in place
86  """
87  scale = self.getInterpImagegetInterpImage(maskedImage.getBBox())
88  maskedImage *= scale
89 
90  def getInterpImage(self, bbox):
91  """Return an image containing the scale correction with same bounding box as supplied.
92 
93  @param[in] bbox: integer bounding box for image (geom.Box2I)
94  """
95  npoints = len(self._xList_xList)
96 
97  if npoints < 1:
98  raise RuntimeError("Cannot create scaling image. Found no fluxMag0s to interpolate")
99 
100  image = afwImage.ImageF(bbox, numpy.mean(self._scaleList_scaleList))
101 
102  return image
103 
104 
105 class ScaleZeroPointConfig(pexConfig.Config):
106  """Config for ScaleZeroPointTask
107  """
108  zeroPoint = pexConfig.Field(
109  dtype=float,
110  doc="desired photometric zero point",
111  default=27.0,
112  )
113 
114 
116  selectFluxMag0 = pexConfig.ConfigurableField(
117  doc="Task to select data to compute spatially varying photometric zeropoint",
118  target=BaseSelectImagesTask,
119  )
120 
121  interpStyle = pexConfig.ChoiceField(
122  dtype=str,
123  doc="Algorithm to interpolate the flux scalings;"
124  "Currently only one choice implemented",
125  default="CONSTANT",
126  allowed={
127  "CONSTANT": "Use a single constant value",
128  }
129  )
130 
131 
132 class ScaleZeroPointTask(pipeBase.Task):
133  """Compute scale factor to scale exposures to a desired photometric zero point
134 
135  This simple version assumes that the zero point is spatially invariant.
136  """
137  ConfigClass = ScaleZeroPointConfig
138  _DefaultName = "scaleZeroPoint"
139 
140  def __init__(self, *args, **kwargs):
141  """Construct a ScaleZeroPointTask
142  """
143  pipeBase.Task.__init__(self, *args, **kwargs)
144 
145  # flux at mag=0 is 10^(zeroPoint/2.5) because m = -2.5*log10(F/F0)
146  fluxMag0 = 10**(0.4 * self.config.zeroPoint)
147  self._photoCalib_photoCalib = afwImage.makePhotoCalibFromCalibZeroPoint(fluxMag0, 0.0)
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.computeImageScalercomputeImageScaler(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.scaleFromPhotoCalibscaleFromPhotoCalib(exposure.getPhotoCalib()).scale
175  return ImageScaler(scale)
176 
177  def getPhotoCalib(self):
178  """Get desired PhotoCalib
179 
180  @return calibration (lsst.afw.image.PhotoCalib) with fluxMag0 set appropriately for config.zeroPoint
181  """
182  return self._photoCalib_photoCalib
183 
184  def scaleFromPhotoCalib(self, calib):
185  """Compute the scale for the specified PhotoCalib
186 
187  Compute scale, such that if pixelCalib describes the photometric zeropoint of a pixel
188  then the following scales that pixel to the photometric zeropoint specified by config.zeroPoint:
189  scale = computeScale(pixelCalib)
190  pixel *= scale
191 
192  @return a pipeBase.Struct containing:
193  - scale, as described above.
194 
195  @note: returns a struct to leave room for scaleErr in a future implementation.
196  """
197  fluxAtZeroPoint = calib.magnitudeToInstFlux(self.config.zeroPoint)
198  return pipeBase.Struct(
199  scale=1.0 / fluxAtZeroPoint,
200  )
201 
202  def scaleFromFluxMag0(self, fluxMag0):
203  """Compute the scale for the specified fluxMag0
204 
205  This is a wrapper around scaleFromPhotoCalib, which see for more information
206 
207  @param[in] fluxMag0
208  @return a pipeBase.Struct containing:
209  - scale, as described in scaleFromPhotoCalib.
210  """
211  calib = afwImage.makePhotoCalibFromCalibZeroPoint(fluxMag0, 0.0)
212  return self.scaleFromPhotoCalibscaleFromPhotoCalib(calib)
213 
214 
216  """Compute spatially varying scale factor to scale exposures to a desired photometric zero point
217  """
218  ConfigClass = SpatialScaleZeroPointConfig
219  _DefaultName = "scaleZeroPoint"
220 
221  def __init__(self, *args, **kwargs):
222  ScaleZeroPointTask.__init__(self, *args, **kwargs)
223  self.makeSubtask("selectFluxMag0")
224 
225  def run(self, exposure, dataRef):
226  """Scale the specified exposure to the desired photometric zeropoint
227 
228  @param[in,out] exposure: exposure to scale; masked image is scaled in place
229  @param[in] dataRef: dataRef for exposure
230 
231  @return a pipeBase.Struct containing:
232  - imageScaler: the image scaling object used to scale exposure
233  """
234  imageScaler = self.computeImageScalercomputeImageScalercomputeImageScaler(exposure=exposure, dataRef=dataRef)
235  mi = exposure.getMaskedImage()
236  imageScaler.scaleMaskedImage(mi)
237  return pipeBase.Struct(
238  imageScaler=imageScaler,
239  )
240 
241  def computeImageScaler(self, exposure, dataRef):
242  """Compute image scaling object for a given exposure.
243 
244  @param[in] exposure: exposure for which scaling is desired. Only wcs and bbox are used.
245  @param[in] dataRef: dataRef of exposure
246  dataRef.dataId used to retrieve all applicable fluxMag0's from a database.
247  @return a SpatialImageScaler
248  """
249 
250  wcs = exposure.getWcs()
251 
252  fluxMagInfoList = self.selectFluxMag0.run(dataRef.dataId).fluxMagInfoList
253 
254  xList = []
255  yList = []
256  scaleList = []
257 
258  for fluxMagInfo in fluxMagInfoList:
259  # find center of field in tract coordinates
260  if not fluxMagInfo.coordList:
261  raise RuntimeError("no x,y data for fluxMagInfo")
262  ctr = geom.Extent2D()
263  for coord in fluxMagInfo.coordList:
264  # accumulate x, y
265  ctr += geom.Extent2D(wcs.skyToPixel(coord))
266  # and find average x, y as the center of the chip
267  ctr = geom.Point2D(ctr / len(fluxMagInfo.coordList))
268  xList.append(ctr.getX())
269  yList.append(ctr.getY())
270  scaleList.append(self.scaleFromFluxMag0scaleFromFluxMag0(fluxMagInfo.fluxMag0).scale)
271 
272  self.log.info("Found %d flux scales for interpolation: %s",
273  len(scaleList), [f"{s:%0.4f}" for s in scaleList])
274  return SpatialImageScaler(
275  interpStyle=self.config.interpStyle,
276  xList=xList,
277  yList=yList,
278  scaleList=scaleList,
279  )
def computeImageScaler(self, exposure, dataRef=None)
def __init__(self, interpStyle, xList, yList, scaleList)
Backwards-compatibility support for depersisting the old Calib (FluxMag0/FluxMag0Err) objects.
std::shared_ptr< PhotoCalib > makePhotoCalibFromCalibZeroPoint(double instFluxMag0, double instFluxMag0Err)
Construct a PhotoCalib from the deprecated Calib-style instFluxMag0/instFluxMag0Err values.
Definition: PhotoCalib.cc:613