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