23 __all__ = (
"SubtractBackgroundConfig",
"SubtractBackgroundTask")
25 from builtins
import zip
30 from lsstDebug
import getDebugFrame
31 import lsst.afw.display
as afwDisplay
33 import lsst.afw.math
as afwMath
34 import lsst.pex.config
as pexConfig
35 import lsst.pipe.base
as pipeBase
36 from functools
import reduce
40 """!Config for SubtractBackgroundTask 42 @note Many of these fields match fields in lsst.afw.math.BackgroundControl, 43 the control class for lsst.afw.math.makeBackground 45 statisticsProperty = pexConfig.ChoiceField(
46 doc=
"type of statistic to use for grid points",
47 dtype=str, default=
"MEANCLIP",
49 "MEANCLIP":
"clipped mean",
50 "MEAN":
"unclipped mean",
54 undersampleStyle = pexConfig.ChoiceField(
55 doc=
"behaviour if there are too few points in grid for requested interpolation style",
56 dtype=str, default=
"REDUCE_INTERP_ORDER",
58 "THROW_EXCEPTION":
"throw an exception if there are too few points",
59 "REDUCE_INTERP_ORDER":
"use an interpolation style with a lower order.",
60 "INCREASE_NXNYSAMPLE":
"Increase the number of samples used to make the interpolation grid.",
63 binSize = pexConfig.RangeField(
64 doc=
"how large a region of the sky should be used for each background point",
65 dtype=int, default=128, min=1,
67 binSizeX = pexConfig.RangeField(
68 doc=(
"Sky region size to be used for each background point in X direction. " 69 "If 0, the binSize config is used."),
70 dtype=int, default=0, min=0,
72 binSizeY = pexConfig.RangeField(
73 doc=(
"Sky region size to be used for each background point in Y direction. " 74 "If 0, the binSize config is used."),
75 dtype=int, default=0, min=0,
77 algorithm = pexConfig.ChoiceField(
78 doc=
"how to interpolate the background values. This maps to an enum; see afw::math::Background",
79 dtype=str, default=
"AKIMA_SPLINE", optional=
True,
81 "CONSTANT":
"Use a single constant value",
82 "LINEAR":
"Use linear interpolation",
83 "NATURAL_SPLINE":
"cubic spline with zero second derivative at endpoints",
84 "AKIMA_SPLINE":
"higher-level nonlinear spline that is more robust to outliers",
85 "NONE":
"No background estimation is to be attempted",
88 ignoredPixelMask = pexConfig.ListField(
89 doc=
"Names of mask planes to ignore while estimating the background",
90 dtype=str, default=[
"BAD",
"EDGE",
"DETECTED",
"DETECTED_NEGATIVE",
"NO_DATA", ],
91 itemCheck=
lambda x: x
in afwImage.Mask().getMaskPlaneDict().keys(),
93 isNanSafe = pexConfig.Field(
94 doc=
"Ignore NaNs when estimating the background",
95 dtype=bool, default=
False,
98 useApprox = pexConfig.Field(
99 doc=
"Use Approximate (Chebyshev) to model background.",
100 dtype=bool, default=
True,
102 approxOrderX = pexConfig.Field(
103 doc=
"Approximation order in X for background Chebyshev (valid only with useApprox=True)",
104 dtype=int, default=6,
109 approxOrderY = pexConfig.Field(
110 doc=
"Approximation order in Y for background Chebyshev (valid only with useApprox=True)",
111 dtype=int, default=-1,
113 weighting = pexConfig.Field(
114 doc=
"Use inverse variance weighting in calculation (valid only with useApprox=True)",
115 dtype=bool, default=
True,
127 """!Subtract the background from an exposure 129 @anchor SubtractBackgroundTask_ 131 @section meas_algorithms_subtractBackground_Contents Contents 133 - @ref meas_algorithms_subtractBackground_Purpose 134 - @ref meas_algorithms_subtractBackground_Initialize 135 - @ref meas_algorithms_subtractBackground_IO 136 - @ref meas_algorithms_subtractBackground_Config 137 - @ref meas_algorithms_subtractBackground_Metadata 138 - @ref meas_algorithms_subtractBackground_Debug 139 - @ref meas_algorithms_subtractBackground_Example 141 @section meas_algorithms_subtractBackground_Purpose Description 143 Fit a model of the background of an exposure and subtract it. 145 @section meas_algorithms_subtractBackground_Initialize Task initialisation 147 @copydoc \_\_init\_\_ 149 @section meas_algorithms_subtractBackground_IO Invoking the Task 151 Call `run` to fit the background and subtract it. 153 Call `fitBackground` to fit the background without subtracting it. 155 @section meas_algorithms_subtractBackground_Config Configuration parameters 157 See @ref SubtractBackgroundConfig 159 @section meas_algorithms_subtractBackground_Metadata Quantities set in exposure Metadata 161 The `run` method will optionally set the following items of exposure metadata; 162 the names may be overridden; the defaults are shown: 164 <dt>BGMEAN <dd>mean value of background 165 <dt>BGVAR <dd>standard deviation of background 168 @section meas_algorithms_subtractBackground_Debug Debug variables 170 The @link lsst.pipe.base.cmdLineTask.CmdLineTask command line task@endlink interface supports a flag 171 `--debug` to import `debug.py` from your `$PYTHONPATH`; see @ref baseDebug for more about `debug.py`. 173 SubtractBackgroundTask has a debug dictionary containing three integer keys: 176 <dd>If >0: `fitBackground` displays the unsubtracted masked image overlaid with the grid of cells 177 used to fit the background in the specified frame 179 <dd>If >0: `run` displays the background-subtracted exposure is the specified frame 181 <dd>If >0: `run` displays the background image in the specified frame 184 For example, put something like: 188 di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively 189 if name == "lsst.meas.algorithms.subtractBackground": 198 lsstDebug.Info = DebugInfo 200 into your `debug.py` file and run your task with the `--debug` flag. 202 @section meas_algorithms_subtractBackground_Example A complete example of using SubtractBackgroundTask 204 This code is in @link subtractBackgroundExample.py@endlink in the examples directory, and can be run as: 206 python examples/subtractBackgroundExample.py 208 @dontinclude subtractBackgroundExample.py 210 Import the task (there are some other standard imports; read the file if you're curious) 211 @skipline import SubtractBackgroundTask 213 Create the task, run it, and report mean and variance of background. 214 @skip create the task 217 ConfigClass = SubtractBackgroundConfig
218 _DefaultName =
"subtractBackground" 220 def run(self, exposure, background=None, stats=True, statsKeys=None):
221 """!Fit and subtract the background of an exposure 223 @param[in,out] exposure exposure whose background is to be subtracted 224 @param[in,out] background initial background model already subtracted from exposure 225 (an lsst.afw.math.BackgroundList). May be None if no background has been subtracted. 226 @param[in] stats if True then measure the mean and variance of the full background model 227 and record the results in the exposure's metadata 228 @param[in] statsKeys key names used to store the mean and variance of the background 229 in the exposure's metadata (a pair of strings); if None then use ("BGMEAN", "BGVAR"); 230 ignored if stats is false 232 @return an lsst.pipe.base.Struct containing: 233 - background full background model (initial model with changes), an lsst.afw.math.BackgroundList 235 if background
is None:
236 background = afwMath.BackgroundList()
238 maskedImage = exposure.getMaskedImage()
240 maskedImage -= fitBg.getImageF()
241 background.append(fitBg)
244 self.
_addStats(exposure, background, statsKeys=statsKeys)
246 subFrame = getDebugFrame(self._display,
"subtracted")
248 subDisp = afwDisplay.getDisplay(frame=subFrame)
249 subDisp.mtv(exposure, title=
"subtracted")
251 bgFrame = getDebugFrame(self._display,
"background")
253 bgDisp = afwDisplay.getDisplay(frame=bgFrame)
254 bgImage = background.getImage()
255 bgDisp.mtv(bgImage, title=
"background")
257 return pipeBase.Struct(
258 background=background,
261 def _addStats(self, exposure, background, statsKeys=None):
262 """Add statistics about the background to the exposure's metadata 264 @param[in,out] exposure exposure whose background was subtracted 265 @param[in,out] background background model (an lsst.afw.math.BackgroundList) 266 @param[in] statsKeys key names used to store the mean and variance of the background 267 in the exposure's metadata (a pair of strings); if None then use ("BGMEAN", "BGVAR"); 268 ignored if stats is false 270 netBgImg = background.getImage()
271 if statsKeys
is None:
272 statsKeys = (
"BGMEAN",
"BGVAR")
273 mnkey, varkey = statsKeys
274 meta = exposure.getMetadata()
275 s = afwMath.makeStatistics(netBgImg, afwMath.MEAN | afwMath.VARIANCE)
276 bgmean = s.getValue(afwMath.MEAN)
277 bgvar = s.getValue(afwMath.VARIANCE)
278 meta.addDouble(mnkey, bgmean)
279 meta.addDouble(varkey, bgvar)
282 """!Estimate the background of a masked image 284 @param[in] maskedImage masked image whose background is to be computed 285 @param[in] nx number of x bands; if 0 compute from width and config.binSizeX 286 @param[in] ny number of y bands; if 0 compute from height and config.binSizeY 287 @param[in] algorithm name of interpolation algorithm; if None use self.config.algorithm 289 @return fit background as an lsst.afw.math.Background 291 @throw RuntimeError if lsst.afw.math.makeBackground returns None, 292 which is apparently one way it indicates failure 295 binSizeX = self.config.binSize
if self.config.binSizeX == 0
else self.config.binSizeX
296 binSizeY = self.config.binSize
if self.config.binSizeY == 0
else self.config.binSizeY
299 nx = maskedImage.getWidth()//binSizeX + 1
301 ny = maskedImage.getHeight()//binSizeY + 1
303 unsubFrame = getDebugFrame(self._display,
"unsubtracted")
305 unsubDisp = afwDisplay.getDisplay(frame=unsubFrame)
306 unsubDisp.mtv(maskedImage, title=
"unsubtracted")
307 xPosts = numpy.rint(numpy.linspace(0, maskedImage.getWidth() + 1, num=nx, endpoint=
True))
308 yPosts = numpy.rint(numpy.linspace(0, maskedImage.getHeight() + 1, num=ny, endpoint=
True))
309 with unsubDisp.Buffering():
310 for (xMin, xMax), (yMin, yMax)
in itertools.product(zip(xPosts[:-1], xPosts[1:]),
311 zip(yPosts[:-1], yPosts[1:])):
312 unsubDisp.line([(xMin, yMin), (xMin, yMax), (xMax, yMax), (xMax, yMin), (xMin, yMin)])
314 sctrl = afwMath.StatisticsControl()
315 sctrl.setAndMask(reduce(
lambda x, y: x | maskedImage.getMask().getPlaneBitMask(y),
316 self.config.ignoredPixelMask, 0x0))
317 sctrl.setNanSafe(self.config.isNanSafe)
319 self.log.debug(
"Ignoring mask planes: %s" %
", ".join(self.config.ignoredPixelMask))
321 if algorithm
is None:
322 algorithm = self.config.algorithm
324 bctrl = afwMath.BackgroundControl(algorithm, nx, ny,
325 self.config.undersampleStyle, sctrl,
326 self.config.statisticsProperty)
340 if self.config.useApprox:
341 if self.config.approxOrderY
not in (self.config.approxOrderX, -1):
342 raise ValueError(
"Error: approxOrderY not in (approxOrderX, -1)")
343 order = self.config.approxOrderX
344 minNumberGridPoints = order + 1
345 if min(nx, ny) <= order:
346 self.log.warn(
"Too few points in grid to constrain fit: min(nx, ny) < approxOrder) " 347 "[min(%d, %d) < %d]" % (nx, ny, order))
348 if self.config.undersampleStyle ==
"THROW_EXCEPTION":
349 raise ValueError(
"Too few points in grid (%d, %d) for order (%d) and binSize (%d, %d)" %
350 (nx, ny, order, binSizeX, binSizeY))
351 elif self.config.undersampleStyle ==
"REDUCE_INTERP_ORDER":
353 raise ValueError(
"Cannot reduce approxOrder below 0. " +
354 "Try using undersampleStyle = \"INCREASE_NXNYSAMPLE\" instead?")
355 order = min(nx, ny) - 1
356 self.log.warn(
"Reducing approxOrder to %d" % order)
357 elif self.config.undersampleStyle ==
"INCREASE_NXNYSAMPLE":
359 newBinSize = min(maskedImage.getWidth(), maskedImage.getHeight())//(minNumberGridPoints-1)
361 raise ValueError(
"Binsize must be greater than 0")
362 newNx = maskedImage.getWidth()//newBinSize + 1
363 newNy = maskedImage.getHeight()//newBinSize + 1
364 bctrl.setNxSample(newNx)
365 bctrl.setNySample(newNy)
366 self.log.warn(
"Decreasing binSize from (%d, %d) to %d for a grid of (%d, %d)" %
367 (binSizeX, binSizeY, newBinSize, newNx, newNy))
369 actrl = afwMath.ApproximateControl(afwMath.ApproximateControl.CHEBYSHEV, order, order,
370 self.config.weighting)
371 bctrl.setApproximateControl(actrl)
373 bg = afwMath.makeBackground(maskedImage, bctrl)
375 raise RuntimeError(
"lsst.afw.math.makeBackground failed to fit a background model")
def _addStats(self, exposure, background, statsKeys=None)
def fitBackground(self, maskedImage, nx=0, ny=0, algorithm=None)
Estimate the background of a masked image.
Config for SubtractBackgroundTask.
Subtract the background from an exposure.
def run(self, exposure, background=None, stats=True, statsKeys=None)
Fit and subtract the background of an exposure.