23 __all__ = (
"SubtractBackgroundConfig",
"SubtractBackgroundTask")
29 from lsstDebug
import getDebugFrame
35 import lsst.pex.config
as pexConfig
37 from functools
import reduce
41 """!Config for SubtractBackgroundTask 43 @note Many of these fields match fields in lsst.afw.math.BackgroundControl, 44 the control class for lsst.afw.math.makeBackground 46 statisticsProperty = pexConfig.ChoiceField(
47 doc=
"type of statistic to use for grid points",
48 dtype=str, default=
"MEANCLIP",
50 "MEANCLIP":
"clipped mean",
51 "MEAN":
"unclipped mean",
55 undersampleStyle = pexConfig.ChoiceField(
56 doc=
"behaviour if there are too few points in grid for requested interpolation style",
57 dtype=str, default=
"REDUCE_INTERP_ORDER",
59 "THROW_EXCEPTION":
"throw an exception if there are too few points",
60 "REDUCE_INTERP_ORDER":
"use an interpolation style with a lower order.",
61 "INCREASE_NXNYSAMPLE":
"Increase the number of samples used to make the interpolation grid.",
64 binSize = pexConfig.RangeField(
65 doc=
"how large a region of the sky should be used for each background point",
66 dtype=int, default=128, min=1,
68 binSizeX = pexConfig.RangeField(
69 doc=(
"Sky region size to be used for each background point in X direction. " 70 "If 0, the binSize config is used."),
71 dtype=int, default=0, min=0,
73 binSizeY = pexConfig.RangeField(
74 doc=(
"Sky region size to be used for each background point in Y direction. " 75 "If 0, the binSize config is used."),
76 dtype=int, default=0, min=0,
78 algorithm = pexConfig.ChoiceField(
79 doc=
"how to interpolate the background values. This maps to an enum; see afw::math::Background",
80 dtype=str, default=
"AKIMA_SPLINE", optional=
True,
82 "CONSTANT":
"Use a single constant value",
83 "LINEAR":
"Use linear interpolation",
84 "NATURAL_SPLINE":
"cubic spline with zero second derivative at endpoints",
85 "AKIMA_SPLINE":
"higher-level nonlinear spline that is more robust to outliers",
86 "NONE":
"No background estimation is to be attempted",
89 ignoredPixelMask = pexConfig.ListField(
90 doc=
"Names of mask planes to ignore while estimating the background",
91 dtype=str, default=[
"BAD",
"EDGE",
"DETECTED",
"DETECTED_NEGATIVE",
"NO_DATA", ],
92 itemCheck=
lambda x: x
in afwImage.Mask().getMaskPlaneDict().keys(),
94 isNanSafe = pexConfig.Field(
95 doc=
"Ignore NaNs when estimating the background",
96 dtype=bool, default=
False,
99 useApprox = pexConfig.Field(
100 doc=
"Use Approximate (Chebyshev) to model background.",
101 dtype=bool, default=
True,
103 approxOrderX = pexConfig.Field(
104 doc=
"Approximation order in X for background Chebyshev (valid only with useApprox=True)",
105 dtype=int, default=6,
110 approxOrderY = pexConfig.Field(
111 doc=
"Approximation order in Y for background Chebyshev (valid only with useApprox=True)",
112 dtype=int, default=-1,
114 weighting = pexConfig.Field(
115 doc=
"Use inverse variance weighting in calculation (valid only with useApprox=True)",
116 dtype=bool, default=
True,
128 r"""!Subtract the background from an exposure 130 @anchor SubtractBackgroundTask_ 132 @section meas_algorithms_subtractBackground_Contents Contents 134 - @ref meas_algorithms_subtractBackground_Purpose 135 - @ref meas_algorithms_subtractBackground_Initialize 136 - @ref meas_algorithms_subtractBackground_IO 137 - @ref meas_algorithms_subtractBackground_Config 138 - @ref meas_algorithms_subtractBackground_Metadata 139 - @ref meas_algorithms_subtractBackground_Debug 140 - @ref meas_algorithms_subtractBackground_Example 142 @section meas_algorithms_subtractBackground_Purpose Description 144 Fit a model of the background of an exposure and subtract it. 146 @section meas_algorithms_subtractBackground_Initialize Task initialisation 148 @copydoc \_\_init\_\_ 150 @section meas_algorithms_subtractBackground_IO Invoking the Task 152 Call `run` to fit the background and subtract it. 154 Call `fitBackground` to fit the background without subtracting it. 156 @section meas_algorithms_subtractBackground_Config Configuration parameters 158 See @ref SubtractBackgroundConfig 160 @section meas_algorithms_subtractBackground_Metadata Quantities set in exposure Metadata 162 The `run` method will optionally set the following items of exposure metadata; 163 the names may be overridden; the defaults are shown: 165 <dt>BGMEAN <dd>mean value of background 166 <dt>BGVAR <dd>standard deviation of background 169 @section meas_algorithms_subtractBackground_Debug Debug variables 171 The @link lsst.pipe.base.cmdLineTask.CmdLineTask command line task@endlink interface supports a flag 172 `--debug` to import `debug.py` from your `$PYTHONPATH`; see @ref baseDebug for more about `debug.py`. 174 SubtractBackgroundTask has a debug dictionary containing three integer keys: 177 <dd>If >0: `fitBackground` displays the unsubtracted masked image overlaid with the grid of cells 178 used to fit the background in the specified frame 180 <dd>If >0: `run` displays the background-subtracted exposure is the specified frame 182 <dd>If >0: `run` displays the background image in the specified frame 185 For example, put something like: 189 di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively 190 if name == "lsst.meas.algorithms.subtractBackground": 199 lsstDebug.Info = DebugInfo 201 into your `debug.py` file and run your task with the `--debug` flag. 203 @section meas_algorithms_subtractBackground_Example A complete example of using SubtractBackgroundTask 205 This code is in @link subtractBackgroundExample.py@endlink in the examples directory, and can be run as: 207 python examples/subtractBackgroundExample.py 209 @dontinclude subtractBackgroundExample.py 211 Import the task (there are some other standard imports; read the file if you're curious) 212 @skipline import SubtractBackgroundTask 214 Create the task, run it, and report mean and variance of background. 215 @skip create the task 218 ConfigClass = SubtractBackgroundConfig
219 _DefaultName =
"subtractBackground" 221 def run(self, exposure, background=None, stats=True, statsKeys=None):
222 """!Fit and subtract the background of an exposure 224 @param[in,out] exposure exposure whose background is to be subtracted 225 @param[in,out] background initial background model already subtracted from exposure 226 (an lsst.afw.math.BackgroundList). May be None if no background has been subtracted. 227 @param[in] stats if True then measure the mean and variance of the full background model 228 and record the results in the exposure's metadata 229 @param[in] statsKeys key names used to store the mean and variance of the background 230 in the exposure's metadata (a pair of strings); if None then use ("BGMEAN", "BGVAR"); 231 ignored if stats is false 233 @return an lsst.pipe.base.Struct containing: 234 - background full background model (initial model with changes), an lsst.afw.math.BackgroundList 236 if background
is None:
237 background = afwMath.BackgroundList()
239 maskedImage = exposure.getMaskedImage()
241 maskedImage -= fitBg.getImageF(self.config.algorithm, self.config.undersampleStyle)
243 actrl = fitBg.getBackgroundControl().getApproximateControl()
244 background.append((fitBg, getattr(afwMath.Interpolate, self.config.algorithm),
245 fitBg.getAsUsedUndersampleStyle(), actrl.getStyle(),
246 actrl.getOrderX(), actrl.getOrderY(), actrl.getWeighting()))
249 self.
_addStats(exposure, background, statsKeys=statsKeys)
251 subFrame = getDebugFrame(self._display,
"subtracted")
253 subDisp = afwDisplay.getDisplay(frame=subFrame)
254 subDisp.mtv(exposure, title=
"subtracted")
256 bgFrame = getDebugFrame(self._display,
"background")
258 bgDisp = afwDisplay.getDisplay(frame=bgFrame)
259 bgImage = background.getImage()
260 bgDisp.mtv(bgImage, title=
"background")
262 return pipeBase.Struct(
263 background=background,
266 def _addStats(self, exposure, background, statsKeys=None):
267 """Add statistics about the background to the exposure's metadata 269 @param[in,out] exposure exposure whose background was subtracted 270 @param[in,out] background background model (an lsst.afw.math.BackgroundList) 271 @param[in] statsKeys key names used to store the mean and variance of the background 272 in the exposure's metadata (a pair of strings); if None then use ("BGMEAN", "BGVAR"); 273 ignored if stats is false 275 netBgImg = background.getImage()
276 if statsKeys
is None:
277 statsKeys = (
"BGMEAN",
"BGVAR")
278 mnkey, varkey = statsKeys
279 meta = exposure.getMetadata()
280 s = afwMath.makeStatistics(netBgImg, afwMath.MEAN | afwMath.VARIANCE)
281 bgmean = s.getValue(afwMath.MEAN)
282 bgvar = s.getValue(afwMath.VARIANCE)
283 meta.addDouble(mnkey, bgmean)
284 meta.addDouble(varkey, bgvar)
287 """!Estimate the background of a masked image 289 @param[in] maskedImage masked image whose background is to be computed 290 @param[in] nx number of x bands; if 0 compute from width and config.binSizeX 291 @param[in] ny number of y bands; if 0 compute from height and config.binSizeY 292 @param[in] algorithm name of interpolation algorithm; if None use self.config.algorithm 294 @return fit background as an lsst.afw.math.Background 296 @throw RuntimeError if lsst.afw.math.makeBackground returns None, 297 which is apparently one way it indicates failure 300 binSizeX = self.config.binSize
if self.config.binSizeX == 0
else self.config.binSizeX
301 binSizeY = self.config.binSize
if self.config.binSizeY == 0
else self.config.binSizeY
304 nx = maskedImage.getWidth()//binSizeX + 1
306 ny = maskedImage.getHeight()//binSizeY + 1
308 unsubFrame = getDebugFrame(self._display,
"unsubtracted")
310 unsubDisp = afwDisplay.getDisplay(frame=unsubFrame)
311 unsubDisp.mtv(maskedImage, title=
"unsubtracted")
312 xPosts = numpy.rint(numpy.linspace(0, maskedImage.getWidth() + 1, num=nx, endpoint=
True))
313 yPosts = numpy.rint(numpy.linspace(0, maskedImage.getHeight() + 1, num=ny, endpoint=
True))
314 with unsubDisp.Buffering():
315 for (xMin, xMax), (yMin, yMax)
in itertools.product(zip(xPosts[:-1], xPosts[1:]),
316 zip(yPosts[:-1], yPosts[1:])):
317 unsubDisp.line([(xMin, yMin), (xMin, yMax), (xMax, yMax), (xMax, yMin), (xMin, yMin)])
319 sctrl = afwMath.StatisticsControl()
320 sctrl.setAndMask(reduce(
lambda x, y: x | maskedImage.getMask().getPlaneBitMask(y),
321 self.config.ignoredPixelMask, 0x0))
322 sctrl.setNanSafe(self.config.isNanSafe)
324 self.log.debug(
"Ignoring mask planes: %s" %
", ".join(self.config.ignoredPixelMask))
326 if algorithm
is None:
327 algorithm = self.config.algorithm
335 with suppress_deprecations():
336 bctrl = afwMath.BackgroundControl(algorithm, nx, ny,
337 self.config.undersampleStyle, sctrl,
338 self.config.statisticsProperty)
352 if self.config.useApprox:
353 if self.config.approxOrderY
not in (self.config.approxOrderX, -1):
354 raise ValueError(
"Error: approxOrderY not in (approxOrderX, -1)")
355 order = self.config.approxOrderX
356 minNumberGridPoints = order + 1
357 if min(nx, ny) <= order:
358 self.log.warn(
"Too few points in grid to constrain fit: min(nx, ny) < approxOrder) " 359 "[min(%d, %d) < %d]" % (nx, ny, order))
360 if self.config.undersampleStyle ==
"THROW_EXCEPTION":
361 raise ValueError(
"Too few points in grid (%d, %d) for order (%d) and binSize (%d, %d)" %
362 (nx, ny, order, binSizeX, binSizeY))
363 elif self.config.undersampleStyle ==
"REDUCE_INTERP_ORDER":
365 raise ValueError(
"Cannot reduce approxOrder below 0. " 366 "Try using undersampleStyle = \"INCREASE_NXNYSAMPLE\" instead?")
367 order = min(nx, ny) - 1
368 self.log.warn(
"Reducing approxOrder to %d" % order)
369 elif self.config.undersampleStyle ==
"INCREASE_NXNYSAMPLE":
371 newBinSize = min(maskedImage.getWidth(), maskedImage.getHeight())//(minNumberGridPoints-1)
373 raise ValueError(
"Binsize must be greater than 0")
374 newNx = maskedImage.getWidth()//newBinSize + 1
375 newNy = maskedImage.getHeight()//newBinSize + 1
376 bctrl.setNxSample(newNx)
377 bctrl.setNySample(newNy)
378 self.log.warn(
"Decreasing binSize from (%d, %d) to %d for a grid of (%d, %d)" %
379 (binSizeX, binSizeY, newBinSize, newNx, newNy))
381 actrl = afwMath.ApproximateControl(afwMath.ApproximateControl.CHEBYSHEV, order, order,
382 self.config.weighting)
383 bctrl.setApproximateControl(actrl)
385 bg = afwMath.makeBackground(maskedImage, bctrl)
387 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.