Loading [MathJax]/extensions/tex2jax.js
LSST Applications g0fba68d861+05816baf74,g1ec0fe41b4+f536777771,g1fd858c14a+a9301854fb,g35bb328faa+fcb1d3bbc8,g4af146b050+a5c07d5b1d,g4d2262a081+6e5fcc2a4e,g53246c7159+fcb1d3bbc8,g56a49b3a55+9c12191793,g5a012ec0e7+3632fc3ff3,g60b5630c4e+ded28b650d,g67b6fd64d1+ed4b5058f4,g78460c75b0+2f9a1b4bcd,g786e29fd12+cf7ec2a62a,g8352419a5c+fcb1d3bbc8,g87b7deb4dc+7b42cf88bf,g8852436030+e5453db6e6,g89139ef638+ed4b5058f4,g8e3bb8577d+d38d73bdbd,g9125e01d80+fcb1d3bbc8,g94187f82dc+ded28b650d,g989de1cb63+ed4b5058f4,g9d31334357+ded28b650d,g9f33ca652e+50a8019d8c,gabe3b4be73+1e0a283bba,gabf8522325+fa80ff7197,gb1101e3267+d9fb1f8026,gb58c049af0+f03b321e39,gb665e3612d+2a0c9e9e84,gb89ab40317+ed4b5058f4,gcf25f946ba+e5453db6e6,gd6cbbdb0b4+bb83cc51f8,gdd1046aedd+ded28b650d,gde0f65d7ad+941d412827,ge278dab8ac+d65b3c2b70,ge410e46f29+ed4b5058f4,gf23fb2af72+b7cae620c0,gf5e32f922b+fcb1d3bbc8,gf67bdafdda+ed4b5058f4,w.2025.16
LSST Data Management Base Package
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
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
23from __future__ import annotations
24
25__all__ = ["ImageScaler", "SpatialImageScaler", "ScaleZeroPointTask"]
26
27import numpy
28import lsst.geom as geom
29import lsst.afw.image as afwImage
30import lsst.pex.config as pexConfig
31import lsst.pipe.base as pipeBase
32from lsst.pipe.tasks.selectImages import BaseSelectImagesTask
33from deprecated.sphinx import deprecated
34
35
37 """A class that scales an image.
38
39 This version uses a single scalar. Fancier versions may use a spatially varying scale.
40
41 Parameters
42 ----------
43 scale : `float`, optional
44 Scale correction to apply (see ``scaleMaskedImage``).
45 """
46
47 def __init__(self, scale=1.0):
48 self._scale = scale
49
50 # TODO: Remove this property in DM-49402.
51 @property
52 @deprecated("This property will be removed after v30.", version="v30", category=FutureWarning)
53 def scale(self) -> float:
54 """Scale that it applies to a specified image."""
55 return self._scale
56
57 def scaleMaskedImage(self, maskedImage):
58 """Scale the specified image or masked image in place.
59
60 Parameters
61 ----------
62 maskedImage : `lsst.afw.image.MaskedImage`
63 Masked image to scale.
64 """
65 maskedImage *= self._scale
66
67
69 """Multiplicative image scaler using interpolation over a grid of points.
70
71 Contains the x, y positions in tract coordinates and the scale factors.
72 Interpolates only when scaleMaskedImage() or getInterpImage() is called.
73
74 Currently the only type of 'interpolation' implemented is CONSTANT which calculates the mean.
75
76 Parameters
77 ----------
78 interpStyle : `Unknown`
79 Interpolation style (`CONSTANT` is only option).
80 xList : `list` of `int`
81 List of X pixel positions.
82 yList : `list` of `int`
83 List of Y pixel positions.
84 scaleList : `Unknown`
85 List of multiplicative scale factors at (x,y).
86
87 Raises
88 ------
89 RuntimeError
90 Raised if the lists have different lengths.
91 """
92
93 def __init__(self, interpStyle, xList, yList, scaleList):
94 if len(xList) != len(yList) or len(xList) != len(scaleList):
95 raise RuntimeError(
96 "len(xList)=%s len(yList)=%s, len(scaleList)=%s but all lists must have the same length" %
97 (len(xList), len(yList), len(scaleList)))
98
99 # Eventually want this do be: self.interpStyle = getattr(afwMath.Interpolate2D, interpStyle)
100 self._xList = xList
101 self._yList = yList
102 self._scaleList = scaleList
103
104 # TODO: Remove this property in DM-49402.
105 @property
106 @deprecated("This property will be removed after v30.", version="v30", category=FutureWarning)
107 def scale(self) -> float:
108 """Mean scale that it applies to a specified image."""
109 return numpy.mean(self._scaleList)
110
111 def scaleMaskedImage(self, maskedImage):
112 """Apply scale correction to the specified masked image.
113
114 Parameters
115 ----------
116 maskedImage : `lsst.afw.image.MaskedImage`
117 Masked image to scale; scale is applied in place.
118 """
119 scale = self.getInterpImage(maskedImage.getBBox())
120 maskedImage *= scale
121
122 def getInterpImage(self, bbox):
123 """Return an image containing the scale correction with same bounding box as supplied.
124
125 Parameters
126 ----------
127 bbox : `lsst.geom.Box2I`
128 Integer bounding box for image.
129
130 Raises
131 ------
132 RuntimeError
133 Raised if there are no fluxMag0s to interpolate.
134 """
135 npoints = len(self._xList)
136
137 if npoints < 1:
138 raise RuntimeError("Cannot create scaling image. Found no fluxMag0s to interpolate")
139
140 image = afwImage.ImageF(bbox, numpy.mean(self._scaleList))
141
142 return image
143
144
145class ScaleZeroPointConfig(pexConfig.Config):
146 """Config for ScaleZeroPointTask.
147 """
148
149 zeroPoint = pexConfig.Field(
150 dtype=float,
151 doc="desired photometric zero point",
152 default=27.0,
153 )
154
155
157 selectFluxMag0 = pexConfig.ConfigurableField(
158 doc="Task to select data to compute spatially varying photometric zeropoint",
159 target=BaseSelectImagesTask,
160 )
161
162 interpStyle = pexConfig.ChoiceField(
163 dtype=str,
164 doc="Algorithm to interpolate the flux scalings;"
165 "Currently only one choice implemented",
166 default="CONSTANT",
167 allowed={
168 "CONSTANT": "Use a single constant value",
169 }
170 )
171
172
173class ScaleZeroPointTask(pipeBase.Task):
174 """Compute scale factor to scale exposures to a desired photometric zero point.
175
176 This simple version assumes that the zero point is spatially invariant.
177 """
178
179 ConfigClass = ScaleZeroPointConfig
180 _DefaultName = "scaleZeroPoint"
181
182 def __init__(self, *args, **kwargs):
183 pipeBase.Task.__init__(self, *args, **kwargs)
184
185 # flux at mag=0 is 10^(zeroPoint/2.5) because m = -2.5*log10(F/F0)
186 fluxMag0 = 10**(0.4 * self.config.zeroPoint)
188
189 def run(self, exposure, dataRef=None):
190 """Scale the specified exposure to the desired photometric zeropoint.
191
192 Parameters
193 ----------
194 exposure : `lsst.afw.image.Exposure`
195 Exposure to scale; masked image is scaled in place.
196 dataRef : `Unknown`, optional
197 Data reference for exposure.
198 Not used, but in API so that users can switch between spatially variant
199 and invariant tasks.
200
201 Returns
202 -------
203 result : `~lsst.pipe.base.Struct`
204 Results as a struct with attributes:
205
206 ``imageScaler``
207 The image scaling object used to scale exposure.
208 """
209 imageScaler = self.computeImageScaler(exposure=exposure, dataRef=dataRef)
210 mi = exposure.getMaskedImage()
211 imageScaler.scaleMaskedImage(mi)
212 return pipeBase.Struct(
213 imageScaler=imageScaler,
214 )
215
216 def computeImageScaler(self, exposure, dataRef=None):
217 """Compute image scaling object for a given exposure.
218
219 Parameters
220 ----------
221 exposure : `lsst.afw.image.Exposure`
222 Exposure for which scaling is desired.
223 dataRef : `Unknown`, optional
224 Data reference for exposure.
225 Not used, but in API so that users can switch between spatially variant
226 and invariant tasks.
227 """
228 scale = self.scaleFromPhotoCalib(exposure.getPhotoCalib()).scale
229 return ImageScaler(scale)
230
231 def getPhotoCalib(self):
232 """Get desired PhotoCalib.
233
234 Returns
235 -------
236 calibration : `lsst.afw.image.PhotoCalib`
237 Calibration with ``fluxMag0`` set appropriately for config.zeroPoint.
238 """
239 return self._photoCalib
240
241 def scaleFromPhotoCalib(self, calib):
242 """Compute the scale for the specified PhotoCalib.
243
244 Parameter
245 ---------
246 calib : `lsst.afw.image.PhotoCalib`
247 PhotoCalib object to compute the scale from.
248
249 Returns
250 -------
251 result : `lsst.pipe.base.Struct`
252 Results as a struct with attributes:
253
254 `scale`
255
256 Scale, such that if pixelCalib describes the photometric
257 zeropoint of a pixel then the following scales that pixel to
258 the photometric zeropoint specified by config.zeroPoint:
259 ``scale = computeScale(pixelCalib) pixel *= scale``
260
261 Notes
262 -----
263 Returns a struct to leave room for scaleErr in a future implementation.
264 """
265 fluxAtZeroPoint = calib.magnitudeToInstFlux(self.config.zeroPoint)
266 return pipeBase.Struct(
267 scale=1.0 / fluxAtZeroPoint,
268 )
269
270 def scaleFromFluxMag0(self, fluxMag0):
271 """Compute the scale for the specified fluxMag0.
272
273 This is a wrapper around scaleFromPhotoCalib, which see for more information.
274
275 Parameters
276 ----------
277 fluxMag0 : `float`
278 Flux at magnitude zero.
279
280 Returns
281 -------
282 result : `lsst.pipe.base.Struct`
283 Results as a struct with attributes:
284
285 `scale`
286
287 Scale, such that if pixelCalib describes the photometric zeropoint
288 of a pixel then the following scales that pixel to the photometric
289 zeropoint specified by config.zeroPoint:
290 ``scale = computeScale(pixelCalib)``
291 ``pixel *= scale``
292 """
293 calib = afwImage.makePhotoCalibFromCalibZeroPoint(fluxMag0, 0.0)
294 return self.scaleFromPhotoCalib(calib)
295
296
298 """Compute spatially varying scale factor to scale exposures to a desired photometric zero point.
299 """
300
301 ConfigClass = SpatialScaleZeroPointConfig
302 _DefaultName = "scaleZeroPoint"
303
304 def __init__(self, *args, **kwargs):
305 ScaleZeroPointTask.__init__(self, *args, **kwargs)
306 self.makeSubtask("selectFluxMag0")
307
308 def run(self, exposure, dataRef):
309 """Scale the specified exposure to the desired photometric zeropoint.
310
311 Parameters
312 ----------
313 exposure : `lsst.afw.image.Exposure`
314 Exposure to scale; masked image is scaled in place.
315 dataRef : `Unknown`
316 Data reference for exposure.
317
318 Returns
319 -------
320 result : `lsst.pipe.base.Struct`
321 Results as a struct with attributes:
322
323 ``imageScaler``
324 The image scaling object used to scale exposure.
325 """
326 imageScaler = self.computeImageScaler(exposure=exposure, dataRef=dataRef)
327 mi = exposure.getMaskedImage()
328 imageScaler.scaleMaskedImage(mi)
329 return pipeBase.Struct(
330 imageScaler=imageScaler,
331 )
332
333 def computeImageScaler(self, exposure, dataRef):
334 """Compute image scaling object for a given exposure.
335
336 Parameters
337 ----------
338 exposure : `lsst.afw.image.Exposure`
339 Exposure for which scaling is desired. Only wcs and bbox are used.
340 dataRef : `Unknown`
341 Data reference of exposure.
342 dataRef.dataId used to retrieve all applicable fluxMag0's from a database.
343
344 Returns
345 -------
346 result : `SpatialImageScaler`
347 """
348 wcs = exposure.getWcs()
349
350 fluxMagInfoList = self.selectFluxMag0.run(dataRef.dataId).fluxMagInfoList
351
352 xList = []
353 yList = []
354 scaleList = []
355
356 for fluxMagInfo in fluxMagInfoList:
357 # find center of field in tract coordinates
358 if not fluxMagInfo.coordList:
359 raise RuntimeError("no x,y data for fluxMagInfo")
360 ctr = geom.Extent2D()
361 for coord in fluxMagInfo.coordList:
362 # accumulate x, y
363 ctr += geom.Extent2D(wcs.skyToPixel(coord))
364 # and find average x, y as the center of the chip
365 ctr = geom.Point2D(ctr / len(fluxMagInfo.coordList))
366 xList.append(ctr.getX())
367 yList.append(ctr.getY())
368 scaleList.append(self.scaleFromFluxMag0(fluxMagInfo.fluxMag0).scale)
369
370 self.log.info("Found %d flux scales for interpolation: %s",
371 len(scaleList), [f"{s:%0.4f}" for s in scaleList])
372 return SpatialImageScaler(
373 interpStyle=self.config.interpStyle,
374 xList=xList,
375 yList=yList,
376 scaleList=scaleList,
377 )
__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.