LSSTApplications  18.1.0
LSSTDataManagementBasePackage
subtractBackground.py
Go to the documentation of this file.
1 #
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")
24 
25 import itertools
26 
27 import numpy
28 
29 from lsstDebug import getDebugFrame
30 import lsst.afw.display as afwDisplay
31 import lsst.afw.image as afwImage
32 import lsst.afw.math as afwMath
33 import lsst.pex.config as pexConfig
34 import lsst.pipe.base as pipeBase
35 from functools import reduce
36 
37 
38 class SubtractBackgroundConfig(pexConfig.Config):
39  """!Config for SubtractBackgroundTask
40 
41  @note Many of these fields match fields in lsst.afw.math.BackgroundControl,
42  the control class for lsst.afw.math.makeBackground
43  """
44  statisticsProperty = pexConfig.ChoiceField(
45  doc="type of statistic to use for grid points",
46  dtype=str, default="MEANCLIP",
47  allowed={
48  "MEANCLIP": "clipped mean",
49  "MEAN": "unclipped mean",
50  "MEDIAN": "median",
51  }
52  )
53  undersampleStyle = pexConfig.ChoiceField(
54  doc="behaviour if there are too few points in grid for requested interpolation style",
55  dtype=str, default="REDUCE_INTERP_ORDER",
56  allowed={
57  "THROW_EXCEPTION": "throw an exception if there are too few points",
58  "REDUCE_INTERP_ORDER": "use an interpolation style with a lower order.",
59  "INCREASE_NXNYSAMPLE": "Increase the number of samples used to make the interpolation grid.",
60  },
61  )
62  binSize = pexConfig.RangeField(
63  doc="how large a region of the sky should be used for each background point",
64  dtype=int, default=128, min=1,
65  )
66  binSizeX = pexConfig.RangeField(
67  doc=("Sky region size to be used for each background point in X direction. "
68  "If 0, the binSize config is used."),
69  dtype=int, default=0, min=0,
70  )
71  binSizeY = pexConfig.RangeField(
72  doc=("Sky region size to be used for each background point in Y direction. "
73  "If 0, the binSize config is used."),
74  dtype=int, default=0, min=0,
75  )
76  algorithm = pexConfig.ChoiceField(
77  doc="how to interpolate the background values. This maps to an enum; see afw::math::Background",
78  dtype=str, default="AKIMA_SPLINE", optional=True,
79  allowed={
80  "CONSTANT": "Use a single constant value",
81  "LINEAR": "Use linear interpolation",
82  "NATURAL_SPLINE": "cubic spline with zero second derivative at endpoints",
83  "AKIMA_SPLINE": "higher-level nonlinear spline that is more robust to outliers",
84  "NONE": "No background estimation is to be attempted",
85  },
86  )
87  ignoredPixelMask = pexConfig.ListField(
88  doc="Names of mask planes to ignore while estimating the background",
89  dtype=str, default=["BAD", "EDGE", "DETECTED", "DETECTED_NEGATIVE", "NO_DATA", ],
90  itemCheck=lambda x: x in afwImage.Mask().getMaskPlaneDict().keys(),
91  )
92  isNanSafe = pexConfig.Field(
93  doc="Ignore NaNs when estimating the background",
94  dtype=bool, default=False,
95  )
96 
97  useApprox = pexConfig.Field(
98  doc="Use Approximate (Chebyshev) to model background.",
99  dtype=bool, default=True,
100  )
101  approxOrderX = pexConfig.Field(
102  doc="Approximation order in X for background Chebyshev (valid only with useApprox=True)",
103  dtype=int, default=6,
104  )
105  # Note: Currently X- and Y-orders must be equal due to a limitation in math::Chebyshev1Function2
106  # The following is being added so that the weighting attribute can also be configurable for the
107  # call to afwMath.ApproximateControl
108  approxOrderY = pexConfig.Field(
109  doc="Approximation order in Y for background Chebyshev (valid only with useApprox=True)",
110  dtype=int, default=-1,
111  )
112  weighting = pexConfig.Field(
113  doc="Use inverse variance weighting in calculation (valid only with useApprox=True)",
114  dtype=bool, default=True,
115  )
116 
117 
118 
124 
125 class SubtractBackgroundTask(pipeBase.Task):
126  r"""!Subtract the background from an exposure
127 
128  @anchor SubtractBackgroundTask_
129 
130  @section meas_algorithms_subtractBackground_Contents Contents
131 
132  - @ref meas_algorithms_subtractBackground_Purpose
133  - @ref meas_algorithms_subtractBackground_Initialize
134  - @ref meas_algorithms_subtractBackground_IO
135  - @ref meas_algorithms_subtractBackground_Config
136  - @ref meas_algorithms_subtractBackground_Metadata
137  - @ref meas_algorithms_subtractBackground_Debug
138  - @ref meas_algorithms_subtractBackground_Example
139 
140  @section meas_algorithms_subtractBackground_Purpose Description
141 
142  Fit a model of the background of an exposure and subtract it.
143 
144  @section meas_algorithms_subtractBackground_Initialize Task initialisation
145 
146  @copydoc \_\_init\_\_
147 
148  @section meas_algorithms_subtractBackground_IO Invoking the Task
149 
150  Call `run` to fit the background and subtract it.
151 
152  Call `fitBackground` to fit the background without subtracting it.
153 
154  @section meas_algorithms_subtractBackground_Config Configuration parameters
155 
156  See @ref SubtractBackgroundConfig
157 
158  @section meas_algorithms_subtractBackground_Metadata Quantities set in exposure Metadata
159 
160  The `run` method will optionally set the following items of exposure metadata;
161  the names may be overridden; the defaults are shown:
162  <dl>
163  <dt>BGMEAN <dd>mean value of background
164  <dt>BGVAR <dd>standard deviation of background
165  </dl>
166 
167  @section meas_algorithms_subtractBackground_Debug Debug variables
168 
169  The @link lsst.pipe.base.cmdLineTask.CmdLineTask command line task@endlink interface supports a flag
170  `--debug` to import `debug.py` from your `$PYTHONPATH`; see @ref baseDebug for more about `debug.py`.
171 
172  SubtractBackgroundTask has a debug dictionary containing three integer keys:
173  <dl>
174  <dt>unsubtracted
175  <dd>If >0: `fitBackground` displays the unsubtracted masked image overlaid with the grid of cells
176  used to fit the background in the specified frame
177  <dt>subtracted
178  <dd>If >0: `run` displays the background-subtracted exposure is the specified frame
179  <dt>background
180  <dd>If >0: `run` displays the background image in the specified frame
181  </dl>
182 
183  For example, put something like:
184  @code{.py}
185  import lsstDebug
186  def DebugInfo(name):
187  di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively
188  if name == "lsst.meas.algorithms.subtractBackground":
189  di.display = dict(
190  unsubtracted = 1,
191  subtracted = 2,
192  background = 3,
193  )
194 
195  return di
196 
197  lsstDebug.Info = DebugInfo
198  @endcode
199  into your `debug.py` file and run your task with the `--debug` flag.
200 
201  @section meas_algorithms_subtractBackground_Example A complete example of using SubtractBackgroundTask
202 
203  This code is in @link subtractBackgroundExample.py@endlink in the examples directory, and can be run as:
204  @code
205  python examples/subtractBackgroundExample.py
206  @endcode
207  @dontinclude subtractBackgroundExample.py
208 
209  Import the task (there are some other standard imports; read the file if you're curious)
210  @skipline import SubtractBackgroundTask
211 
212  Create the task, run it, and report mean and variance of background.
213  @skip create the task
214  @until print
215  """
216  ConfigClass = SubtractBackgroundConfig
217  _DefaultName = "subtractBackground"
218 
219  def run(self, exposure, background=None, stats=True, statsKeys=None):
220  """!Fit and subtract the background of an exposure
221 
222  @param[in,out] exposure exposure whose background is to be subtracted
223  @param[in,out] background initial background model already subtracted from exposure
224  (an lsst.afw.math.BackgroundList). May be None if no background has been subtracted.
225  @param[in] stats if True then measure the mean and variance of the full background model
226  and record the results in the exposure's metadata
227  @param[in] statsKeys key names used to store the mean and variance of the background
228  in the exposure's metadata (a pair of strings); if None then use ("BGMEAN", "BGVAR");
229  ignored if stats is false
230 
231  @return an lsst.pipe.base.Struct containing:
232  - background full background model (initial model with changes), an lsst.afw.math.BackgroundList
233  """
234  if background is None:
235  background = afwMath.BackgroundList()
236 
237  maskedImage = exposure.getMaskedImage()
238  fitBg = self.fitBackground(maskedImage)
239  maskedImage -= fitBg.getImageF()
240  background.append(fitBg)
241 
242  if stats:
243  self._addStats(exposure, background, statsKeys=statsKeys)
244 
245  subFrame = getDebugFrame(self._display, "subtracted")
246  if subFrame:
247  subDisp = afwDisplay.getDisplay(frame=subFrame)
248  subDisp.mtv(exposure, title="subtracted")
249 
250  bgFrame = getDebugFrame(self._display, "background")
251  if bgFrame:
252  bgDisp = afwDisplay.getDisplay(frame=bgFrame)
253  bgImage = background.getImage()
254  bgDisp.mtv(bgImage, title="background")
255 
256  return pipeBase.Struct(
257  background=background,
258  )
259 
260  def _addStats(self, exposure, background, statsKeys=None):
261  """Add statistics about the background to the exposure's metadata
262 
263  @param[in,out] exposure exposure whose background was subtracted
264  @param[in,out] background background model (an lsst.afw.math.BackgroundList)
265  @param[in] statsKeys key names used to store the mean and variance of the background
266  in the exposure's metadata (a pair of strings); if None then use ("BGMEAN", "BGVAR");
267  ignored if stats is false
268  """
269  netBgImg = background.getImage()
270  if statsKeys is None:
271  statsKeys = ("BGMEAN", "BGVAR")
272  mnkey, varkey = statsKeys
273  meta = exposure.getMetadata()
274  s = afwMath.makeStatistics(netBgImg, afwMath.MEAN | afwMath.VARIANCE)
275  bgmean = s.getValue(afwMath.MEAN)
276  bgvar = s.getValue(afwMath.VARIANCE)
277  meta.addDouble(mnkey, bgmean)
278  meta.addDouble(varkey, bgvar)
279 
280  def fitBackground(self, maskedImage, nx=0, ny=0, algorithm=None):
281  """!Estimate the background of a masked image
282 
283  @param[in] maskedImage masked image whose background is to be computed
284  @param[in] nx number of x bands; if 0 compute from width and config.binSizeX
285  @param[in] ny number of y bands; if 0 compute from height and config.binSizeY
286  @param[in] algorithm name of interpolation algorithm; if None use self.config.algorithm
287 
288  @return fit background as an lsst.afw.math.Background
289 
290  @throw RuntimeError if lsst.afw.math.makeBackground returns None,
291  which is apparently one way it indicates failure
292  """
293 
294  binSizeX = self.config.binSize if self.config.binSizeX == 0 else self.config.binSizeX
295  binSizeY = self.config.binSize if self.config.binSizeY == 0 else self.config.binSizeY
296 
297  if not nx:
298  nx = maskedImage.getWidth()//binSizeX + 1
299  if not ny:
300  ny = maskedImage.getHeight()//binSizeY + 1
301 
302  unsubFrame = getDebugFrame(self._display, "unsubtracted")
303  if unsubFrame:
304  unsubDisp = afwDisplay.getDisplay(frame=unsubFrame)
305  unsubDisp.mtv(maskedImage, title="unsubtracted")
306  xPosts = numpy.rint(numpy.linspace(0, maskedImage.getWidth() + 1, num=nx, endpoint=True))
307  yPosts = numpy.rint(numpy.linspace(0, maskedImage.getHeight() + 1, num=ny, endpoint=True))
308  with unsubDisp.Buffering():
309  for (xMin, xMax), (yMin, yMax) in itertools.product(zip(xPosts[:-1], xPosts[1:]),
310  zip(yPosts[:-1], yPosts[1:])):
311  unsubDisp.line([(xMin, yMin), (xMin, yMax), (xMax, yMax), (xMax, yMin), (xMin, yMin)])
312 
313  sctrl = afwMath.StatisticsControl()
314  sctrl.setAndMask(reduce(lambda x, y: x | maskedImage.getMask().getPlaneBitMask(y),
315  self.config.ignoredPixelMask, 0x0))
316  sctrl.setNanSafe(self.config.isNanSafe)
317 
318  self.log.debug("Ignoring mask planes: %s" % ", ".join(self.config.ignoredPixelMask))
319 
320  if algorithm is None:
321  algorithm = self.config.algorithm
322 
323  bctrl = afwMath.BackgroundControl(algorithm, nx, ny,
324  self.config.undersampleStyle, sctrl,
325  self.config.statisticsProperty)
326 
327  # TODO: The following check should really be done within lsst.afw.math.
328  # With the current code structure, it would need to be accounted for in the doGetImage()
329  # function in BackgroundMI.cc (which currently only checks against the interpolation settings,
330  # which is not appropriate when useApprox=True)
331  # and/or the makeApproximate() function in afw/Approximate.cc.
332  # See ticket DM-2920: "Clean up code in afw for Approximate background
333  # estimation" (which includes a note to remove the following and the
334  # similar checks in pipe_tasks/matchBackgrounds.py once implemented)
335  #
336  # Check that config setting of approxOrder/binSize make sense
337  # (i.e. ngrid (= shortDimension/binSize) > approxOrderX) and perform
338  # appropriate undersampleStlye behavior.
339  if self.config.useApprox:
340  if self.config.approxOrderY not in (self.config.approxOrderX, -1):
341  raise ValueError("Error: approxOrderY not in (approxOrderX, -1)")
342  order = self.config.approxOrderX
343  minNumberGridPoints = order + 1
344  if min(nx, ny) <= order:
345  self.log.warn("Too few points in grid to constrain fit: min(nx, ny) < approxOrder) "
346  "[min(%d, %d) < %d]" % (nx, ny, order))
347  if self.config.undersampleStyle == "THROW_EXCEPTION":
348  raise ValueError("Too few points in grid (%d, %d) for order (%d) and binSize (%d, %d)" %
349  (nx, ny, order, binSizeX, binSizeY))
350  elif self.config.undersampleStyle == "REDUCE_INTERP_ORDER":
351  if order < 1:
352  raise ValueError("Cannot reduce approxOrder below 0. "
353  "Try using undersampleStyle = \"INCREASE_NXNYSAMPLE\" instead?")
354  order = min(nx, ny) - 1
355  self.log.warn("Reducing approxOrder to %d" % order)
356  elif self.config.undersampleStyle == "INCREASE_NXNYSAMPLE":
357  # Reduce bin size to the largest acceptable square bins
358  newBinSize = min(maskedImage.getWidth(), maskedImage.getHeight())//(minNumberGridPoints-1)
359  if newBinSize < 1:
360  raise ValueError("Binsize must be greater than 0")
361  newNx = maskedImage.getWidth()//newBinSize + 1
362  newNy = maskedImage.getHeight()//newBinSize + 1
363  bctrl.setNxSample(newNx)
364  bctrl.setNySample(newNy)
365  self.log.warn("Decreasing binSize from (%d, %d) to %d for a grid of (%d, %d)" %
366  (binSizeX, binSizeY, newBinSize, newNx, newNy))
367 
368  actrl = afwMath.ApproximateControl(afwMath.ApproximateControl.CHEBYSHEV, order, order,
369  self.config.weighting)
370  bctrl.setApproximateControl(actrl)
371 
372  bg = afwMath.makeBackground(maskedImage, bctrl)
373  if bg is None:
374  raise RuntimeError("lsst.afw.math.makeBackground failed to fit a background model")
375  return bg
def _addStats(self, exposure, background, statsKeys=None)
def fitBackground(self, maskedImage, nx=0, ny=0, algorithm=None)
Estimate the background of a masked image.
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:562
Pass parameters to a Background object.
Definition: Background.h:57
int min
Statistics makeStatistics(lsst::afw::math::MaskedVector< EntryT > const &mv, std::vector< WeightPixel > const &vweights, int const flags, StatisticsControl const &sctrl=StatisticsControl())
The makeStatistics() overload to handle lsst::afw::math::MaskedVector<>
Definition: Statistics.h:520
Control how to make an approximation.
Definition: Approximate.h:48
Pass parameters to a Statistics object.
Definition: Statistics.h:93
Represent a 2-dimensional array of bitmask pixels.
Definition: Mask.h:78
def getDebugFrame(debugDisplay, name)
Definition: lsstDebug.py:90
def run(self, exposure, background=None, stats=True, statsKeys=None)
Fit and subtract the background of an exposure.
Backwards-compatibility support for depersisting the old Calib (FluxMag0/FluxMag0Err) objects...