LSST Applications 27.0.0,g0265f82a02+469cd937ee,g02d81e74bb+21ad69e7e1,g1470d8bcf6+cbe83ee85a,g2079a07aa2+e67c6346a6,g212a7c68fe+04a9158687,g2305ad1205+94392ce272,g295015adf3+81dd352a9d,g2bbee38e9b+469cd937ee,g337abbeb29+469cd937ee,g3939d97d7f+72a9f7b576,g487adcacf7+71499e7cba,g50ff169b8f+5929b3527e,g52b1c1532d+a6fc98d2e7,g591dd9f2cf+df404f777f,g5a732f18d5+be83d3ecdb,g64a986408d+21ad69e7e1,g858d7b2824+21ad69e7e1,g8a8a8dda67+a6fc98d2e7,g99cad8db69+f62e5b0af5,g9ddcbc5298+d4bad12328,ga1e77700b3+9c366c4306,ga8c6da7877+71e4819109,gb0e22166c9+25ba2f69a1,gb6a65358fc+469cd937ee,gbb8dafda3b+69d3c0e320,gc07e1c2157+a98bf949bb,gc120e1dc64+615ec43309,gc28159a63d+469cd937ee,gcf0d15dbbd+72a9f7b576,gdaeeff99f8+a38ce5ea23,ge6526c86ff+3a7c1ac5f1,ge79ae78c31+469cd937ee,gee10cc3b42+a6fc98d2e7,gf1cff7945b+21ad69e7e1,gfbcc870c63+9a11dc8c8f
LSST Data Management Base Package
Loading...
Searching...
No Matches
scaleZeroPoint.py
Go to the documentation of this file.
1# This file is part of pipe_tasks.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <https://www.gnu.org/licenses/>.
21
22__all__ = ["ImageScaler", "SpatialImageScaler", "ScaleZeroPointTask"]
23
24import numpy
25import lsst.geom as geom
26import lsst.afw.image as afwImage
27import lsst.pex.config as pexConfig
28import lsst.pipe.base as pipeBase
29from lsst.pipe.tasks.selectImages import BaseSelectImagesTask
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 Parameters
38 ----------
39 scale : `float`, optional
40 Scale correction to apply (see ``scaleMaskedImage``).
41 """
42
43 def __init__(self, scale=1.0):
44 self._scale = scale
45
46 def scaleMaskedImage(self, maskedImage):
47 """Scale the specified image or masked image in place.
48
49 Parameters
50 ----------
51 maskedImage : `lsst.afw.image.MaskedImage`
52 Masked image to scale.
53 """
54 maskedImage *= self._scale
55
56
58 """Multiplicative image scaler using interpolation over a grid of points.
59
60 Contains the x, y positions in tract coordinates and the scale factors.
61 Interpolates only when scaleMaskedImage() or getInterpImage() is called.
62
63 Currently the only type of 'interpolation' implemented is CONSTANT which calculates the mean.
64
65 Parameters
66 ----------
67 interpStyle : `Unknown`
68 Interpolation style (`CONSTANT` is only option).
69 xList : `list` of `int`
70 List of X pixel positions.
71 yList : `list` of `int`
72 List of Y pixel positions.
73 scaleList : `Unknown`
74 List of multiplicative scale factors at (x,y).
75
76 Raises
77 ------
78 RuntimeError
79 Raised if the lists have different lengths.
80 """
81
82 def __init__(self, interpStyle, xList, yList, scaleList):
83 if len(xList) != len(yList) or len(xList) != len(scaleList):
84 raise RuntimeError(
85 "len(xList)=%s len(yList)=%s, len(scaleList)=%s but all lists must have the same length" %
86 (len(xList), len(yList), len(scaleList)))
87
88 # Eventually want this do be: self.interpStyle = getattr(afwMath.Interpolate2D, interpStyle)
89 self._xList = xList
90 self._yList = yList
91 self._scaleList = scaleList
92
93 def scaleMaskedImage(self, maskedImage):
94 """Apply scale correction to the specified masked image.
95
96 Parameters
97 ----------
98 image : `lsst.afw.image.MaskedImage`
99 To scale; scale is applied in place.
100 """
101 scale = self.getInterpImage(maskedImage.getBBox())
102 maskedImage *= scale
103
104 def getInterpImage(self, bbox):
105 """Return an image containing the scale correction with same bounding box as supplied.
106
107 Parameters
108 ----------
109 bbox : `lsst.geom.Box2I`
110 Integer bounding box for image.
111
112 Raises
113 ------
114 RuntimeError
115 Raised if there are no fluxMag0s to interpolate.
116 """
117 npoints = len(self._xList)
118
119 if npoints < 1:
120 raise RuntimeError("Cannot create scaling image. Found no fluxMag0s to interpolate")
121
122 image = afwImage.ImageF(bbox, numpy.mean(self._scaleList))
123
124 return image
125
126
127class ScaleZeroPointConfig(pexConfig.Config):
128 """Config for ScaleZeroPointTask.
129 """
130
131 zeroPoint = pexConfig.Field(
132 dtype=float,
133 doc="desired photometric zero point",
134 default=27.0,
135 )
136
137
139 selectFluxMag0 = pexConfig.ConfigurableField(
140 doc="Task to select data to compute spatially varying photometric zeropoint",
141 target=BaseSelectImagesTask,
142 )
143
144 interpStyle = pexConfig.ChoiceField(
145 dtype=str,
146 doc="Algorithm to interpolate the flux scalings;"
147 "Currently only one choice implemented",
148 default="CONSTANT",
149 allowed={
150 "CONSTANT": "Use a single constant value",
151 }
152 )
153
154
155class ScaleZeroPointTask(pipeBase.Task):
156 """Compute scale factor to scale exposures to a desired photometric zero point.
157
158 This simple version assumes that the zero point is spatially invariant.
159 """
160
161 ConfigClass = ScaleZeroPointConfig
162 _DefaultName = "scaleZeroPoint"
163
164 def __init__(self, *args, **kwargs):
165 pipeBase.Task.__init__(self, *args, **kwargs)
166
167 # flux at mag=0 is 10^(zeroPoint/2.5) because m = -2.5*log10(F/F0)
168 fluxMag0 = 10**(0.4 * self.config.zeroPoint)
170
171 def run(self, exposure, dataRef=None):
172 """Scale the specified exposure to the desired photometric zeropoint.
173
174 Parameters
175 ----------
176 exposure : `lsst.afw.image.Exposure`
177 Exposure to scale; masked image is scaled in place.
178 dataRef : `Unknown`
179 Data reference for exposure.
180 Not used, but in API so that users can switch between spatially variant
181 and invariant tasks.
182
183 Returns
184 -------
185 result : `lsst.pipe.base.Struct`
186 Results as a struct with attributes:
187
188 ``imageScaler``
189 The image scaling object used to scale exposure.
190 """
191 imageScaler = self.computeImageScaler(exposure=exposure, dataRef=dataRef)
192 mi = exposure.getMaskedImage()
193 imageScaler.scaleMaskedImage(mi)
194 return pipeBase.Struct(
195 imageScaler=imageScaler,
196 )
197
198 def computeImageScaler(self, exposure, dataRef=None):
199 """Compute image scaling object for a given exposure.
200
201 Parameters
202 ----------
203 exposure : `lsst.afw.image.Exposure`
204 Exposure for which scaling is desired.
205 dataRef : `Unknown`, optional
206 Data reference for exposure.
207 Not used, but in API so that users can switch between spatially variant
208 and invariant tasks.
209 """
210 scale = self.scaleFromPhotoCalib(exposure.getPhotoCalib()).scale
211 return ImageScaler(scale)
212
213 def getPhotoCalib(self):
214 """Get desired PhotoCalib.
215
216 Returns
217 -------
218 calibration : `lsst.afw.image.PhotoCalib`
219 Calibration with ``fluxMag0`` set appropriately for config.zeroPoint.
220 """
221 return self._photoCalib
222
223 def scaleFromPhotoCalib(self, calib):
224 """Compute the scale for the specified PhotoCalib.
225
226 Returns
227 -------
228 result : `lsst.pipe.base.Struct`
229 Results as a struct with attributes:
230
231 `scale`
232
233 Scale, such that if pixelCalib describes the photometric
234 zeropoint of a pixel then the following scales that pixel to
235 the photometric zeropoint specified by config.zeroPoint:
236 ``scale = computeScale(pixelCalib) pixel *= scale``
237
238 Notes
239 -----
240 Returns a struct to leave room for scaleErr in a future implementation.
241 """
242 fluxAtZeroPoint = calib.magnitudeToInstFlux(self.config.zeroPoint)
243 return pipeBase.Struct(
244 scale=1.0 / fluxAtZeroPoint,
245 )
246
247 def scaleFromFluxMag0(self, fluxMag0):
248 """Compute the scale for the specified fluxMag0.
249
250 This is a wrapper around scaleFromPhotoCalib, which see for more information.
251
252 Parameters
253 ----------
254 fluxMag0 : `float`
255 Flux at magnitude zero.
256
257 Returns
258 -------
259 result : `lsst.pipe.base.Struct`
260 Results as a struct with attributes:
261
262 `scale`
263
264 Scale, such that if pixelCalib describes the photometric zeropoint
265 of a pixel then the following scales that pixel to the photometric
266 zeropoint specified by config.zeroPoint:
267 ``scale = computeScale(pixelCalib)``
268 ``pixel *= scale``
269 """
270 calib = afwImage.makePhotoCalibFromCalibZeroPoint(fluxMag0, 0.0)
271 return self.scaleFromPhotoCalib(calib)
272
273
275 """Compute spatially varying scale factor to scale exposures to a desired photometric zero point.
276 """
277
278 ConfigClass = SpatialScaleZeroPointConfig
279 _DefaultName = "scaleZeroPoint"
280
281 def __init__(self, *args, **kwargs):
282 ScaleZeroPointTask.__init__(self, *args, **kwargs)
283 self.makeSubtask("selectFluxMag0")
284
285 def run(self, exposure, dataRef):
286 """Scale the specified exposure to the desired photometric zeropoint.
287
288 Parameters
289 ----------
290 exposure : `lsst.afw.image.Exposure`
291 Exposure to scale; masked image is scaled in place.
292 dataRef : `Unknown`
293 Data reference for exposure.
294
295 Returns
296 -------
297 result : `lsst.pipe.base.Struct`
298 Results as a struct with attributes:
299
300 ``imageScaler``
301 The image scaling object used to scale exposure.
302 """
303 imageScaler = self.computeImageScalercomputeImageScaler(exposure=exposure, dataRef=dataRef)
304 mi = exposure.getMaskedImage()
305 imageScaler.scaleMaskedImage(mi)
306 return pipeBase.Struct(
307 imageScaler=imageScaler,
308 )
309
310 def computeImageScaler(self, exposure, dataRef):
311 """Compute image scaling object for a given exposure.
312
313 Parameters
314 ----------
315 exposure : `lsst.afw.image.Exposure`
316 Exposure for which scaling is desired. Only wcs and bbox are used.
317 dataRef : `Unknown`
318 Data reference of exposure.
319 dataRef.dataId used to retrieve all applicable fluxMag0's from a database.
320
321 Returns
322 -------
323 result : `SpatialImageScaler`
324 """
325 wcs = exposure.getWcs()
326
327 fluxMagInfoList = self.selectFluxMag0.run(dataRef.dataId).fluxMagInfoList
328
329 xList = []
330 yList = []
331 scaleList = []
332
333 for fluxMagInfo in fluxMagInfoList:
334 # find center of field in tract coordinates
335 if not fluxMagInfo.coordList:
336 raise RuntimeError("no x,y data for fluxMagInfo")
337 ctr = geom.Extent2D()
338 for coord in fluxMagInfo.coordList:
339 # accumulate x, y
340 ctr += geom.Extent2D(wcs.skyToPixel(coord))
341 # and find average x, y as the center of the chip
342 ctr = geom.Point2D(ctr / len(fluxMagInfo.coordList))
343 xList.append(ctr.getX())
344 yList.append(ctr.getY())
345 scaleList.append(self.scaleFromFluxMag0(fluxMagInfo.fluxMag0).scale)
346
347 self.log.info("Found %d flux scales for interpolation: %s",
348 len(scaleList), [f"{s:%0.4f}" for s in scaleList])
349 return SpatialImageScaler(
350 interpStyle=self.config.interpStyle,
351 xList=xList,
352 yList=yList,
353 scaleList=scaleList,
354 )
__init__(self, interpStyle, xList, yList, scaleList)
std::shared_ptr< PhotoCalib > makePhotoCalibFromCalibZeroPoint(double instFluxMag0, double instFluxMag0Err)
Construct a PhotoCalib from the deprecated Calib-style instFluxMag0/instFluxMag0Err values.