LSST Applications g0b6bd0c080+a72a5dd7e6,g1182afd7b4+2a019aa3bb,g17e5ecfddb+2b8207f7de,g1d67935e3f+06cf436103,g38293774b4+ac198e9f13,g396055baef+6a2097e274,g3b44f30a73+6611e0205b,g480783c3b1+98f8679e14,g48ccf36440+89c08d0516,g4b93dc025c+98f8679e14,g5c4744a4d9+a302e8c7f0,g613e996a0d+e1c447f2e0,g6c8d09e9e7+25247a063c,g7271f0639c+98f8679e14,g7a9cd813b8+124095ede6,g9d27549199+a302e8c7f0,ga1cf026fa3+ac198e9f13,ga32aa97882+7403ac30ac,ga786bb30fb+7a139211af,gaa63f70f4e+9994eb9896,gabf319e997+ade567573c,gba47b54d5d+94dc90c3ea,gbec6a3398f+06cf436103,gc6308e37c7+07dd123edb,gc655b1545f+ade567573c,gcc9029db3c+ab229f5caf,gd01420fc67+06cf436103,gd877ba84e5+06cf436103,gdb4cecd868+6f279b5b48,ge2d134c3d5+cc4dbb2e3f,ge448b5faa6+86d1ceac1d,gecc7e12556+98f8679e14,gf3ee170dca+25247a063c,gf4ac96e456+ade567573c,gf9f5ea5b4d+ac198e9f13,gff490e6085+8c2580be5c,w.2022.27
LSST Data Management Base Package
scaleZeroPoint.py
Go to the documentation of this file.
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#
22import numpy
23import lsst.geom as geom
24import lsst.afw.image as afwImage
25import lsst.pex.config as pexConfig
26import lsst.pipe.base as pipeBase
27from 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
105class 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
132class 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 )
The photometric calibration of an exposure.
Definition: PhotoCalib.h:114
An integer coordinate rectangle.
Definition: Box.h:55
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