23 __all__ = (
"SubtractBackgroundConfig", 
"SubtractBackgroundTask")
 
   29 from lsstDebug 
import getDebugFrame
 
   35 import lsst.pex.config 
as pexConfig
 
   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", ],
 
   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     r"""!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:
 
  238         maskedImage = exposure.getMaskedImage()
 
  240         maskedImage -= fitBg.getImageF(self.config.algorithm, self.config.undersampleStyle)
 
  242         actrl = fitBg.getBackgroundControl().getApproximateControl()
 
  244                            fitBg.getAsUsedUndersampleStyle(), actrl.getStyle(),
 
  245                            actrl.getOrderX(), actrl.getOrderY(), actrl.getWeighting()))
 
  248             self.
_addStats(exposure, background, statsKeys=statsKeys)
 
  252             subDisp = afwDisplay.getDisplay(frame=subFrame)
 
  253             subDisp.mtv(exposure, title=
"subtracted")
 
  257             bgDisp = afwDisplay.getDisplay(frame=bgFrame)
 
  258             bgImage = background.getImage()
 
  259             bgDisp.mtv(bgImage, title=
"background")
 
  261         return pipeBase.Struct(
 
  262             background=background,
 
  265     def _addStats(self, exposure, background, statsKeys=None):
 
  266         """Add statistics about the background to the exposure's metadata 
  268         @param[in,out] exposure  exposure whose background was subtracted 
  269         @param[in,out] background  background model (an lsst.afw.math.BackgroundList) 
  270         @param[in] statsKeys  key names used to store the mean and variance of the background 
  271             in the exposure's metadata (a pair of strings); if None then use ("BGMEAN", "BGVAR"); 
  272             ignored if stats is false 
  274         netBgImg = background.getImage()
 
  275         if statsKeys 
is None:
 
  276             statsKeys = (
"BGMEAN", 
"BGVAR")
 
  277         mnkey, varkey = statsKeys
 
  278         meta = exposure.getMetadata()
 
  280         bgmean = s.getValue(afwMath.MEAN)
 
  281         bgvar = s.getValue(afwMath.VARIANCE)
 
  282         meta.addDouble(mnkey, bgmean)
 
  283         meta.addDouble(varkey, bgvar)
 
  286         """!Estimate the background of a masked image 
  288         @param[in] maskedImage  masked image whose background is to be computed 
  289         @param[in] nx  number of x bands; if 0 compute from width and config.binSizeX 
  290         @param[in] ny  number of y bands; if 0 compute from height and config.binSizeY 
  291         @param[in] algorithm  name of interpolation algorithm; if None use self.config.algorithm 
  293         @return fit background as an lsst.afw.math.Background 
  295         @throw RuntimeError if lsst.afw.math.makeBackground returns None, 
  296             which is apparently one way it indicates failure 
  299         binSizeX = self.config.binSize 
if self.config.binSizeX == 0 
else self.config.binSizeX
 
  300         binSizeY = self.config.binSize 
if self.config.binSizeY == 0 
else self.config.binSizeY
 
  303             nx = maskedImage.getWidth()//binSizeX + 1
 
  305             ny = maskedImage.getHeight()//binSizeY + 1
 
  309             unsubDisp = afwDisplay.getDisplay(frame=unsubFrame)
 
  310             unsubDisp.mtv(maskedImage, title=
"unsubtracted")
 
  311             xPosts = numpy.rint(numpy.linspace(0, maskedImage.getWidth() + 1, num=nx, endpoint=
True))
 
  312             yPosts = numpy.rint(numpy.linspace(0, maskedImage.getHeight() + 1, num=ny, endpoint=
True))
 
  313             with unsubDisp.Buffering():
 
  314                 for (xMin, xMax), (yMin, yMax) 
in itertools.product(zip(xPosts[:-1], xPosts[1:]),
 
  315                                                                     zip(yPosts[:-1], yPosts[1:])):
 
  316                     unsubDisp.line([(xMin, yMin), (xMin, yMax), (xMax, yMax), (xMax, yMin), (xMin, yMin)])
 
  319         badMask = maskedImage.mask.getPlaneBitMask(self.config.ignoredPixelMask)
 
  321         sctrl.setAndMask(badMask)
 
  322         sctrl.setNanSafe(self.config.isNanSafe)
 
  324         self.log.
debug(
"Ignoring mask planes: %s" % 
", ".join(self.config.ignoredPixelMask))
 
  325         if (maskedImage.mask.getArray() & badMask).
all():
 
  326             raise pipeBase.TaskError(
"All pixels masked. Cannot estimate background")
 
  328         if algorithm 
is None:
 
  329             algorithm = self.config.algorithm
 
  339                                               self.config.undersampleStyle, sctrl,
 
  340                                               self.config.statisticsProperty)
 
  354         if self.config.useApprox:
 
  355             if self.config.approxOrderY 
not in (self.config.approxOrderX, -1):
 
  356                 raise ValueError(
"Error: approxOrderY not in (approxOrderX, -1)")
 
  357             order = self.config.approxOrderX
 
  358             minNumberGridPoints = order + 1
 
  359             if min(nx, ny) <= order:
 
  360                 self.log.
warn(
"Too few points in grid to constrain fit: min(nx, ny) < approxOrder) " 
  361                               "[min(%d, %d) < %d]" % (nx, ny, order))
 
  362                 if self.config.undersampleStyle == 
"THROW_EXCEPTION":
 
  363                     raise ValueError(
"Too few points in grid (%d, %d) for order (%d) and binSize (%d, %d)" %
 
  364                                      (nx, ny, order, binSizeX, binSizeY))
 
  365                 elif self.config.undersampleStyle == 
"REDUCE_INTERP_ORDER":
 
  367                         raise ValueError(
"Cannot reduce approxOrder below 0.  " 
  368                                          "Try using undersampleStyle = \"INCREASE_NXNYSAMPLE\" instead?")
 
  369                     order = 
min(nx, ny) - 1
 
  370                     self.log.
warn(
"Reducing approxOrder to %d" % order)
 
  371                 elif self.config.undersampleStyle == 
"INCREASE_NXNYSAMPLE":
 
  373                     newBinSize = 
min(maskedImage.getWidth(), maskedImage.getHeight())//(minNumberGridPoints-1)
 
  375                         raise ValueError(
"Binsize must be greater than 0")
 
  376                     newNx = maskedImage.getWidth()//newBinSize + 1
 
  377                     newNy = maskedImage.getHeight()//newBinSize + 1
 
  378                     bctrl.setNxSample(newNx)
 
  379                     bctrl.setNySample(newNy)
 
  380                     self.log.
warn(
"Decreasing binSize from (%d, %d) to %d for a grid of (%d, %d)" %
 
  381                                   (binSizeX, binSizeY, newBinSize, newNx, newNy))
 
  384                                                self.config.weighting)
 
  385             bctrl.setApproximateControl(actrl)
 
  389             raise RuntimeError(
"lsst.afw.math.makeBackground failed to fit a background model")