Loading [MathJax]/extensions/tex2jax.js
LSST Applications g04a91732dc+cc8870d3f5,g07dc498a13+5aa0b8792f,g0fba68d861+488cddfaa9,g1409bbee79+5aa0b8792f,g1a7e361dbc+5aa0b8792f,g1fd858c14a+f64bc332a9,g35bb328faa+fcb1d3bbc8,g4d2262a081+b1c1982739,g4d39ba7253+9633a327c1,g4e0f332c67+5d362be553,g53246c7159+fcb1d3bbc8,g60b5630c4e+9633a327c1,g668ecb457e+25d63fd678,g78460c75b0+2f9a1b4bcd,g786e29fd12+cf7ec2a62a,g7b71ed6315+fcb1d3bbc8,g8852436030+8b64ca622a,g89139ef638+5aa0b8792f,g89e1512fd8+37f975783e,g8d6b6b353c+9633a327c1,g9125e01d80+fcb1d3bbc8,g989de1cb63+5aa0b8792f,g9f33ca652e+b196626af7,ga9baa6287d+9633a327c1,gaaedd4e678+5aa0b8792f,gabe3b4be73+1e0a283bba,gb1101e3267+71e32094df,gb58c049af0+f03b321e39,gb90eeb9370+2807b1ad02,gc741bbaa4f+1ae86710ed,gcf25f946ba+8b64ca622a,gd315a588df+a39986a76f,gd6cbbdb0b4+c8606af20c,gd9a9a58781+fcb1d3bbc8,gde0f65d7ad+94dfc458f4,ge278dab8ac+932305ba37,ge82c20c137+76d20ab76d,gfe73954cf8+a1301e4c20,w.2025.11
LSST Data Management Base Package
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
subtractBackground.py
Go to the documentation of this file.
2# LSST Data Management System
3#
4# Copyright 2008-2017 AURA/LSST.
5#
6# This product includes software developed by the
7# LSST Project (http://www.lsst.org/).
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 LSST License Statement and
20# the GNU General Public License along with this program. If not,
21# see <https://www.lsstcorp.org/LegalNotices/>.
22#
23__all__ = ("SubtractBackgroundConfig", "SubtractBackgroundTask", "backgroundFlatContext")
24
25import itertools
26
27from contextlib import contextmanager
28import numpy
29
30from lsstDebug import getDebugFrame
31from lsst.utils import suppress_deprecations
32
33import lsst.afw.display as afwDisplay
34import lsst.afw.image as afwImage
35import lsst.afw.math as afwMath
36import lsst.pex.config as pexConfig
37import lsst.pipe.base as pipeBase
38
39
40@contextmanager
41def backgroundFlatContext(maskedImage, doApply, backgroundToPhotometricRatio=None):
42 """Context manager to convert from photometric-flattened to background-
43 flattened image.
44
45 Parameters
46 ----------
47 maskedImage : `lsst.afw.image.MaskedImage`
48 Masked image (image + mask + variance) to convert from a
49 photometrically flat image to an image suitable for background
50 subtraction.
51 doApply : `bool`
52 Apply the conversion? If False, this context manager will not
53 do anything.
54 backgroundToPhotometricRatio : `lsst.afw.image.Image`, optional
55 Image to multiply a photometrically-flattened image by to obtain a
56 background-flattened image.
57 Only used if ``doApply`` is ``True``.
58
59 Yields
60 ------
61 maskedImage : `lsst.afw.image.MaskedImage`
62 Masked image converted into an image suitable for background
63 subtraction.
64
65 Raises
66 ------
67 RuntimeError if doApply is True and no ratio is supplied.
68 ValueError if the ratio is not an `lsst.afw.image.Image`.
69 """
70 if doApply:
71 if backgroundToPhotometricRatio is None:
72 raise RuntimeError("backgroundFlatContext called with doApply=True, "
73 "but without a backgroundToPhotometricRatio")
74 if not isinstance(backgroundToPhotometricRatio, afwImage.Image):
75 raise ValueError("The backgroundToPhotometricRatio must be an lsst.afw.image.Image")
76
77 maskedImage *= backgroundToPhotometricRatio
78
79 try:
80 yield maskedImage
81 finally:
82 if doApply:
83 maskedImage /= backgroundToPhotometricRatio
84
85
86class SubtractBackgroundConfig(pexConfig.Config):
87 """Config for SubtractBackgroundTask
88
89 Many of these fields match fields in `lsst.afw.math.BackgroundControl`,
90 the control class for `lsst.afw.math.makeBackground`
91 """
92 statisticsProperty = pexConfig.ChoiceField(
93 doc="type of statistic to use for grid points",
94 dtype=str, default="MEANCLIP",
95 allowed={
96 "MEANCLIP": "clipped mean",
97 "MEAN": "unclipped mean",
98 "MEDIAN": "median",
99 }
100 )
101 undersampleStyle = pexConfig.ChoiceField(
102 doc="behaviour if there are too few points in grid for requested interpolation style",
103 dtype=str, default="REDUCE_INTERP_ORDER",
104 allowed={
105 "THROW_EXCEPTION": "throw an exception if there are too few points",
106 "REDUCE_INTERP_ORDER": "use an interpolation style with a lower order.",
107 "INCREASE_NXNYSAMPLE": "Increase the number of samples used to make the interpolation grid.",
108 },
109 )
110 binSize = pexConfig.RangeField(
111 doc="how large a region of the sky should be used for each background point",
112 dtype=int, default=128, min=1,
113 )
114 binSizeX = pexConfig.RangeField(
115 doc=("Sky region size to be used for each background point in X direction. "
116 "If 0, the binSize config is used."),
117 dtype=int, default=0, min=0,
118 )
119 binSizeY = pexConfig.RangeField(
120 doc=("Sky region size to be used for each background point in Y direction. "
121 "If 0, the binSize config is used."),
122 dtype=int, default=0, min=0,
123 )
124 algorithm = pexConfig.ChoiceField(
125 doc="how to interpolate the background values. This maps to an enum; see afw::math::Background",
126 dtype=str, default="AKIMA_SPLINE", optional=True,
127 allowed={
128 "CONSTANT": "Use a single constant value",
129 "LINEAR": "Use linear interpolation",
130 "NATURAL_SPLINE": "cubic spline with zero second derivative at endpoints",
131 "AKIMA_SPLINE": "higher-level nonlinear spline that is more robust to outliers",
132 "NONE": "No background estimation is to be attempted",
133 },
134 )
135 ignoredPixelMask = pexConfig.ListField(
136 doc="Names of mask planes to ignore while estimating the background",
137 dtype=str, default=["BAD", "EDGE", "DETECTED", "DETECTED_NEGATIVE", "NO_DATA", ],
138 itemCheck=lambda x: x in afwImage.Mask().getMaskPlaneDict().keys(),
139 )
140 isNanSafe = pexConfig.Field(
141 doc="Ignore NaNs when estimating the background",
142 dtype=bool, default=False,
143 )
144
145 useApprox = pexConfig.Field(
146 doc="Use Approximate (Chebyshev) to model background.",
147 dtype=bool, default=True,
148 )
149 approxOrderX = pexConfig.Field(
150 doc="Approximation order in X for background Chebyshev (valid only with useApprox=True)",
151 dtype=int, default=6,
152 )
153 # Note: Currently X- and Y-orders must be equal due to a limitation in math::Chebyshev1Function2
154 # The following is being added so that the weighting attribute can also be configurable for the
155 # call to afwMath.ApproximateControl
156 approxOrderY = pexConfig.Field(
157 doc="Approximation order in Y for background Chebyshev (valid only with useApprox=True)",
158 dtype=int, default=-1,
159 )
160 weighting = pexConfig.Field(
161 doc="Use inverse variance weighting in calculation (valid only with useApprox=True)",
162 dtype=bool, default=True,
163 )
164 doApplyFlatBackgroundRatio = pexConfig.Field(
165 doc="Convert from a photometrically flat image to one suitable to background subtraction? "
166 "If True, then a backgroundToPhotometricRatio must be supplied to the task run method.",
167 dtype=bool,
168 default=False,
169 )
170
171
172class SubtractBackgroundTask(pipeBase.Task):
173 """Subtract the background from an exposure
174 """
175 ConfigClass = SubtractBackgroundConfig
176 _DefaultName = "subtractBackground"
177
178 def run(self, exposure, background=None, stats=True, statsKeys=None, backgroundToPhotometricRatio=None):
179 """Fit and subtract the background of an exposure.
180
181 Parameters
182 ----------
183 exposure : `lsst.afw.image.Exposure`
184 Exposure whose background is to be subtracted.
185 background : `lsst.afw.math.BackgroundList`
186 Initial background model already subtracted. May be None if no background
187 has been subtracted.
188 stats : `bool`
189 If True then measure the mean and variance of the full background model and
190 record the results in the exposure's metadata.
191 statsKeys : `tuple`
192 Key names used to store the mean and variance of the background in the
193 exposure's metadata (another tuple); if None then use ("BGMEAN", "BGVAR");
194 ignored if stats is false.
195 backgroundToPhotometricRatio : `lsst.afw.image.Image`, optional
196 Image to multiply a photometrically-flattened image by to obtain a
197 background-flattened image.
198 Only used if config.doApplyFlatBackgroundRatio = True.
199
200 Returns
201 -------
202 background : `lsst.afw.math.BackgroundList`
203 Full background model (initial model with changes), contained in an
204 `lsst.pipe.base.Struct`.
205 """
206 if background is None:
207 background = afwMath.BackgroundList()
208
209 maskedImage = exposure.maskedImage
210
212 maskedImage,
213 self.config.doApplyFlatBackgroundRatio,
214 backgroundToPhotometricRatio=backgroundToPhotometricRatio,
215 ):
216 fitBg = self.fitBackground(maskedImage)
217 maskedImage -= fitBg.getImageF(self.config.algorithm, self.config.undersampleStyle)
218
219 actrl = fitBg.getBackgroundControl().getApproximateControl()
220 background.append((fitBg, getattr(afwMath.Interpolate, self.config.algorithm),
221 fitBg.getAsUsedUndersampleStyle(), actrl.getStyle(),
222 actrl.getOrderX(), actrl.getOrderY(), actrl.getWeighting()))
223
224 if stats:
225 self._addStats(exposure, background, statsKeys=statsKeys)
226
227 subFrame = getDebugFrame(self._display, "subtracted")
228 if subFrame:
229 subDisp = afwDisplay.getDisplay(frame=subFrame)
230 subDisp.mtv(exposure, title="subtracted")
231
232 bgFrame = getDebugFrame(self._display, "background")
233 if bgFrame:
234 bgDisp = afwDisplay.getDisplay(frame=bgFrame)
235 bgImage = background.getImage()
236 bgDisp.mtv(bgImage, title="background")
237
238 return pipeBase.Struct(
239 background=background,
240 )
241
242 def _addStats(self, exposure, background, statsKeys=None):
243 """Add statistics about the background to the exposure's metadata
244
245 Parameters
246 ----------
247 exposure : `lsst.afw.image.Exposure`
248 Exposure whose background was subtracted.
249 background : `lsst.afw.math.BackgroundList`
250 Background model
251 statsKeys : `tuple`
252 Key names used to store the mean and variance of the background in
253 the exposure's metadata (a tuple); if None then use
254 ("BGMEAN", "BGVAR"); ignored if stats is false.
255 """
256 netBgImg = background.getImage()
257 if statsKeys is None:
258 statsKeys = ("BGMEAN", "BGVAR")
259 mnkey, varkey = statsKeys
260 meta = exposure.getMetadata()
261 s = afwMath.makeStatistics(netBgImg, afwMath.MEAN | afwMath.VARIANCE)
262 bgmean = s.getValue(afwMath.MEAN)
263 bgvar = s.getValue(afwMath.VARIANCE)
264 meta.addDouble(mnkey, bgmean)
265 meta.addDouble(varkey, bgvar)
266
267 def fitBackground(self, maskedImage, nx=0, ny=0, algorithm=None):
268 """Estimate the background of a masked image
269
270 Parameters
271 ----------
272 maskedImage : `lsst.afw.image.maskedImage`
273 Masked image whose background is to be computed
274 nx : 'int`
275 Number of x bands; if 0 compute from width and `self.config.binSizeX`
276 ny : `int`
277 Number of y bands; if 0 compute from height and `self.config.binSizeY`
278 algorithm : `str`
279 Name of interpolation algorithm; if None use `self.config.algorithm`
280
281 Returns
282 -------
283 bg : `lsst.afw.math.Background`
284 A fit background
285
286 Raises
287 ------
288 RuntimeError
289 Raised if lsst.afw.math.makeBackground returns None, an indicator
290 of failure.
291 """
292
293 binSizeX = self.config.binSize if self.config.binSizeX == 0 else self.config.binSizeX
294 binSizeY = self.config.binSize if self.config.binSizeY == 0 else self.config.binSizeY
295
296 if not nx:
297 nx = maskedImage.getWidth()//binSizeX + 1
298 if not ny:
299 ny = maskedImage.getHeight()//binSizeY + 1
300
301 unsubFrame = getDebugFrame(self._display, "unsubtracted")
302 if unsubFrame:
303 unsubDisp = afwDisplay.getDisplay(frame=unsubFrame)
304 unsubDisp.mtv(maskedImage, title="unsubtracted")
305 xPosts = numpy.rint(numpy.linspace(0, maskedImage.getWidth() + 1, num=nx, endpoint=True))
306 yPosts = numpy.rint(numpy.linspace(0, maskedImage.getHeight() + 1, num=ny, endpoint=True))
307 with unsubDisp.Buffering():
308 for (xMin, xMax), (yMin, yMax) in itertools.product(zip(xPosts[:-1], xPosts[1:]),
309 zip(yPosts[:-1], yPosts[1:])):
310 unsubDisp.line([(xMin, yMin), (xMin, yMax), (xMax, yMax), (xMax, yMin), (xMin, yMin)])
311
313 badMask = maskedImage.mask.getPlaneBitMask(self.config.ignoredPixelMask)
314
315 sctrl.setAndMask(badMask)
316 sctrl.setNanSafe(self.config.isNanSafe)
317
318 self.log.debug("Ignoring mask planes: %s", ", ".join(self.config.ignoredPixelMask))
319 if (maskedImage.mask.getArray() & badMask).all():
320 raise pipeBase.TaskError("All pixels masked. Cannot estimate background")
321
322 if algorithm is None:
323 algorithm = self.config.algorithm
324
325 # TODO: DM-22814. This call to a deprecated BackgroundControl constructor
326 # is necessary to support the algorithm parameter; it # should be replaced with
327 #
328 # afwMath.BackgroundControl(nx, ny, sctrl, self.config.statisticsProperty)
329 #
330 # when algorithm has been deprecated and removed.
331 with suppress_deprecations():
332 bctrl = afwMath.BackgroundControl(algorithm, nx, ny,
333 self.config.undersampleStyle, sctrl,
334 self.config.statisticsProperty)
335
336 # TODO: The following check should really be done within lsst.afw.math.
337 # With the current code structure, it would need to be accounted for in the doGetImage()
338 # function in BackgroundMI.cc (which currently only checks against the interpolation settings,
339 # which is not appropriate when useApprox=True)
340 # and/or the makeApproximate() function in afw/Approximate.cc.
341 # See ticket DM-2920: "Clean up code in afw for Approximate background
342 # estimation" (which includes a note to remove the following and the
343 # similar checks in pipe_tasks/matchBackgrounds.py once implemented)
344 #
345 # Check that config setting of approxOrder/binSize make sense
346 # (i.e. ngrid (= shortDimension/binSize) > approxOrderX) and perform
347 # appropriate undersampleStlye behavior.
348 if self.config.useApprox:
349 if self.config.approxOrderY not in (self.config.approxOrderX, -1):
350 raise ValueError("Error: approxOrderY not in (approxOrderX, -1)")
351 order = self.config.approxOrderX
352 minNumberGridPoints = order + 1
353 if min(nx, ny) <= order:
354 self.log.warning("Too few points in grid to constrain fit: min(nx, ny) < approxOrder) "
355 "[min(%d, %d) < %d]", nx, ny, order)
356 if self.config.undersampleStyle == "THROW_EXCEPTION":
357 raise ValueError("Too few points in grid (%d, %d) for order (%d) and binSize (%d, %d)" %
358 (nx, ny, order, binSizeX, binSizeY))
359 elif self.config.undersampleStyle == "REDUCE_INTERP_ORDER":
360 if order < 1:
361 raise ValueError("Cannot reduce approxOrder below 0. "
362 "Try using undersampleStyle = \"INCREASE_NXNYSAMPLE\" instead?")
363 order = min(nx, ny) - 1
364 self.log.warning("Reducing approxOrder to %d", order)
365 elif self.config.undersampleStyle == "INCREASE_NXNYSAMPLE":
366 # Reduce bin size to the largest acceptable square bins
367 newBinSize = min(maskedImage.getWidth(), maskedImage.getHeight())//(minNumberGridPoints-1)
368 if newBinSize < 1:
369 raise ValueError("Binsize must be greater than 0")
370 newNx = maskedImage.getWidth()//newBinSize + 1
371 newNy = maskedImage.getHeight()//newBinSize + 1
372 bctrl.setNxSample(newNx)
373 bctrl.setNySample(newNy)
374 self.log.warning("Decreasing binSize from (%d, %d) to %d for a grid of (%d, %d)",
375 binSizeX, binSizeY, newBinSize, newNx, newNy)
376
377 actrl = afwMath.ApproximateControl(afwMath.ApproximateControl.CHEBYSHEV, order, order,
378 self.config.weighting)
379 bctrl.setApproximateControl(actrl)
380
381 bg = afwMath.makeBackground(maskedImage, bctrl)
382 if bg is None:
383 raise RuntimeError("lsst.afw.math.makeBackground failed to fit a background model")
384 return bg
A class to represent a 2-dimensional array of pixels.
Definition Image.h:51
Represent a 2-dimensional array of bitmask pixels.
Definition Mask.h:82
Control how to make an approximation.
Definition Approximate.h:48
Pass parameters to a Background object.
Definition Background.h:56
Pass parameters to a Statistics object.
Definition Statistics.h:83
run(self, exposure, background=None, stats=True, statsKeys=None, backgroundToPhotometricRatio=None)
fitBackground(self, maskedImage, nx=0, ny=0, algorithm=None)
std::shared_ptr< Background > makeBackground(ImageT const &img, BackgroundControl const &bgCtrl)
A convenience function that uses function overloading to make the correct type of Background.
Definition Background.h:526
Statistics makeStatistics(lsst::afw::image::Image< Pixel > const &img, lsst::afw::image::Mask< image::MaskPixel > const &msk, int const flags, StatisticsControl const &sctrl=StatisticsControl())
Handle a watered-down front-end to the constructor (no variance)
Definition Statistics.h:361
backgroundFlatContext(maskedImage, doApply, backgroundToPhotometricRatio=None)