LSSTApplications  8.0.0.0+107,8.0.0.1+13,9.1+18,9.2,master-g084aeec0a4,master-g0aced2eed8+6,master-g15627eb03c,master-g28afc54ef9,master-g3391ba5ea0,master-g3d0fb8ae5f,master-g4432ae2e89+36,master-g5c3c32f3ec+17,master-g60f1e072bb+1,master-g6a3ac32d1b,master-g76a88a4307+1,master-g7bce1f4e06+57,master-g8ff4092549+31,master-g98e65bf68e,master-ga6b77976b1+53,master-gae20e2b580+3,master-gb584cd3397+53,master-gc5448b162b+1,master-gc54cf9771d,master-gc69578ece6+1,master-gcbf758c456+22,master-gcec1da163f+63,master-gcf15f11bcc,master-gd167108223,master-gf44c96c709
LSSTDataManagementBasePackage
detection.py
Go to the documentation of this file.
1 
2 # LSST Data Management System
3 # Copyright 2008, 2009, 2010, 2011 LSST Corporation.
4 #
5 # This product includes software developed by the
6 # LSST Project (http://www.lsst.org/).
7 #
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the LSST License Statement and
19 # the GNU General Public License along with this program. If not,
20 # see <http://www.lsstcorp.org/LegalNotices/>.
21 #
22 import lsstDebug
23 import lsst.pex.logging as pexLogging
24 
25 import lsst.pex.config as pexConfig
26 import lsst.afw.math as afwMath
27 import lsst.afw.table as afwTable
28 import lsst.afw.image as afwImage
29 import lsst.afw.geom as afwGeom
30 import lsst.afw.detection as afwDet
31 import lsst.pipe.base as pipeBase
32 
33 __all__ = ("SourceDetectionConfig", "SourceDetectionTask", "getBackground",
34  "estimateBackground", "BackgroundConfig", "addExposures")
35 
36 import lsst.afw.display.ds9 as ds9
37 
38 class BackgroundConfig(pexConfig.Config):
39  """!Config for background estimation
40  """
41  statisticsProperty = pexConfig.ChoiceField(
42  doc="type of statistic to use for grid points",
43  dtype=str, default="MEANCLIP",
44  allowed={
45  "MEANCLIP": "clipped mean",
46  "MEAN": "unclipped mean",
47  "MEDIAN": "median",
48  }
49  )
50  undersampleStyle = pexConfig.ChoiceField(
51  doc="behaviour if there are too few points in grid for requested interpolation style",
52  dtype=str, default="REDUCE_INTERP_ORDER",
53  allowed={
54  "THROW_EXCEPTION": "throw an exception if there are too few points",
55  "REDUCE_INTERP_ORDER": "use an interpolation style with a lower order.",
56  "INCREASE_NXNYSAMPLE": "Increase the number of samples used to make the interpolation grid.",
57  }
58  )
59  binSize = pexConfig.RangeField(
60  doc="how large a region of the sky should be used for each background point",
61  dtype=int, default=256, min=10
62  )
63  algorithm = pexConfig.ChoiceField(
64  doc="how to interpolate the background values. This maps to an enum; see afw::math::Background",
65  dtype=str, default="NATURAL_SPLINE", optional=True,
66  allowed={
67  "CONSTANT" : "Use a single constant value",
68  "LINEAR" : "Use linear interpolation",
69  "NATURAL_SPLINE" : "cubic spline with zero second derivative at endpoints",
70  "AKIMA_SPLINE": "higher-level nonlinear spline that is more robust to outliers",
71  "NONE": "No background estimation is to be attempted",
72  }
73  )
74  ignoredPixelMask = pexConfig.ListField(
75  doc="Names of mask planes to ignore while estimating the background",
76  dtype=str, default = ["EDGE", "DETECTED", "DETECTED_NEGATIVE"],
77  itemCheck = lambda x: x in afwImage.MaskU().getMaskPlaneDict().keys(),
78  )
79  isNanSafe = pexConfig.Field(
80  doc="Ignore NaNs when estimating the background",
81  dtype=bool, default=False,
82  )
83 
84  def validate(self):
85  pexConfig.Config.validate(self)
86  # Allow None to be used as an equivalent for "NONE", even though C++ expects the latter.
87  if self.algorithm is None:
88  self.algorithm = "NONE"
89 
90 class SourceDetectionConfig(pexConfig.Config):
91  """!Configuration parameters for the SourceDetectionTask
92  """
93  minPixels = pexConfig.RangeField(
94  doc="detected sources with fewer than the specified number of pixels will be ignored",
95  dtype=int, optional=False, default=1, min=0,
96  )
97  isotropicGrow = pexConfig.Field(
98  doc="Pixels should be grown as isotropically as possible (slower)",
99  dtype=bool, optional=False, default=False,
100  )
101  nSigmaToGrow = pexConfig.Field(
102  doc="Grow detections by nSigmaToGrow * sigma; if 0 then do not grow",
103  dtype=float, default=2.4, # 2.4 pixels/sigma is roughly one pixel/FWHM
104  )
105  returnOriginalFootprints = pexConfig.Field(
106  doc="Grow detections to set the image mask bits, but return the original (not-grown) footprints",
107  dtype=bool, optional=False, default=True # TODO: set default to False once we have a deblender; ticket #2138
108  )
109  thresholdValue = pexConfig.RangeField(
110  doc="Threshold for footprints",
111  dtype=float, optional=False, default=5.0, min=0.0,
112  )
113  includeThresholdMultiplier = pexConfig.RangeField(
114  doc="Include threshold relative to thresholdValue",
115  dtype=float, default=1.0, min=0.0,
116  )
117  thresholdType = pexConfig.ChoiceField(
118  doc="specifies the desired flavor of Threshold",
119  dtype=str, optional=False, default="stdev",
120  allowed={
121  "variance": "threshold applied to image variance",
122  "stdev": "threshold applied to image std deviation",
123  "value": "threshold applied to image value",
124  "pixel_stdev": "threshold applied to per-pixel std deviation",
125  }
126  )
127  thresholdPolarity = pexConfig.ChoiceField(
128  doc="specifies whether to detect positive, or negative sources, or both",
129  dtype=str, optional=False, default="positive",
130  allowed={
131  "positive": "detect only positive sources",
132  "negative": "detect only negative sources",
133  "both": "detect both positive and negative sources",
134  }
135  )
136  adjustBackground = pexConfig.Field(
137  dtype = float,
138  doc = "Fiddle factor to add to the background; debugging only",
139  default = 0.0,
140  )
141  reEstimateBackground = pexConfig.Field(
142  dtype = bool,
143  doc = "Estimate the background again after final source detection?",
144  default = True, optional=False,
145  )
146  background = pexConfig.ConfigField(
147  dtype=BackgroundConfig,
148  doc="Background re-estimation configuration"
149  )
150 
151 ## \addtogroup LSST_task_documentation
152 ## \{
153 ## \page sourceDetectionTask
154 ## \ref SourceDetectionTask_ "SourceDetectionTask"
155 ## \copybrief SourceDetectionTask
156 ## \}
157 
158 class SourceDetectionTask(pipeBase.Task):
159  """!
160 \anchor SourceDetectionTask_
161 
162 \brief Detect positive and negative sources on an exposure and return a new \link table.SourceCatalog\endlink.
163 
164 \section meas_algorithms_detection_Contents Contents
165 
166  - \ref meas_algorithms_detection_Purpose
167  - \ref meas_algorithms_detection_Initialize
168  - \ref meas_algorithms_detection_Invoke
169  - \ref meas_algorithms_detection_Config
170  - \ref meas_algorithms_detection_Debug
171  - \ref meas_algorithms_detection_Example
172 
173 \section meas_algorithms_detection_Purpose Description
174 
175 \copybrief SourceDetectionTask
176 
177 \section meas_algorithms_detection_Initialize Task initialisation
178 
179 \copydoc init
180 
181 \section meas_algorithms_detection_Invoke Invoking the Task
182 
183 \copydoc run
184 
185 \section meas_algorithms_detection_Config Configuration parameters
186 
187 See \ref SourceDetectionConfig
188 
189 \section meas_algorithms_detection_Debug Debug variables
190 
191 The \link lsst.pipe.base.cmdLineTask.CmdLineTask command line task\endlink interface supports a
192 flag \c -d to import \b debug.py from your \c PYTHONPATH; see \ref baseDebug for more about \b debug.py files.
193 
194 The available variables in SourceDetectionTask are:
195 <DL>
196  <DT> \c display
197  <DD>
198  - If True, display the exposure on ds9's frame 0. +ve detections in blue, -ve detections in cyan
199  - If display > 1, display the convolved exposure on frame 1
200 </DL>
201 
202 \section meas_algorithms_detection_Example A complete example of using SourceDetectionTask
203 
204 This code is in \link measAlgTasks.py\endlink in the examples directory, and can be run as \em e.g.
205 \code
206 examples/measAlgTasks.py --ds9
207 \endcode
208 \dontinclude measAlgTasks.py
209 The example also runs the SourceMeasurementTask; see \ref meas_algorithms_measurement_Example for more explanation.
210 
211 Import the task (there are some other standard imports; read the file if you're confused)
212 \skipline SourceDetectionTask
213 
214 We need to create our task before processing any data as the task constructor
215 can add an extra column to the schema, but first we need an almost-empty Schema
216 \skipline makeMinimalSchema
217 after which we can call the constructor:
218 \skip SourceDetectionTask.ConfigClass
219 \until detectionTask
220 
221 We're now ready to process the data (we could loop over multiple exposures/catalogues using the same
222 task objects). First create the output table:
223 \skipline afwTable
224 
225 And process the image
226 \skipline result
227 (You may not be happy that the threshold was set in the config before creating the Task rather than being set
228 separately for each exposure. You \em can reset it just before calling the run method if you must, but we
229 should really implement a better solution).
230 
231 We can then unpack and use the results:
232 \skip sources
233 \until print
234 
235 <HR>
236 To investigate the \ref meas_algorithms_detection_Debug, put something like
237 \code{.py}
238  import lsstDebug
239  def DebugInfo(name):
240  di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively
241  if name == "lsst.meas.algorithms.detection":
242  di.display = 1
243 
244  return di
245 
246  lsstDebug.Info = DebugInfo
247 \endcode
248 into your debug.py file and run measAlgTasks.py with the \c --debug flag.
249  """
250  ConfigClass = SourceDetectionConfig
251  _DefaultName = "sourceDetection"
252 
253  # Need init as well as __init__ because "\copydoc __init__" fails (doxygen bug 732264)
254  def init(self, schema=None, **kwds):
255  """!Create the detection task. Most arguments are simply passed onto pipe.base.Task.
256 
257  \param schema An lsst::afw::table::Schema used to create the output lsst.afw.table.SourceCatalog
258  \param **kwds Keyword arguments passed to lsst.pipe.base.task.Task.__init__.
259 
260  If schema is not None, a 'flags.negative' field will be added to label detections
261  made with a negative threshold.
262 
263  \note This task can add fields to the schema, so any code calling this task must ensure that
264  these columns are indeed present in the input match list; see \ref Example
265  """
266  self.__init__(schema, **kwds)
267 
268  def __init__(self, schema=None, **kwds):
269  """!Create the detection task. See SourceDetectionTask.init for documentation
270  """
271  pipeBase.Task.__init__(self, **kwds)
272  if schema is not None:
273  if schema.getVersion() == 0:
274  self.negativeFlagKey = schema.addField(
275  "flags.negative", type="Flag",
276  doc="set if source was detected as significantly negative"
277  )
278  else:
279  self.negativeFlagKey = schema.addField(
280  "flags_negative", type="Flag",
281  doc="set if source was detected as significantly negative"
282  )
283  else:
284  if self.config.thresholdPolarity == "both":
285  self.log.log(self.log.WARN, "Detection polarity set to 'both', but no flag will be "\
286  "set to distinguish between positive and negative detections")
287  self.negativeFlagKey = None
288 
289  @pipeBase.timeMethod
290  def run(self, table, exposure, doSmooth=True, sigma=None, clearMask=True):
291  """!Run source detection and create a SourceCatalog.
292 
293  \param table lsst.afw.table.SourceTable object that will be used to create the SourceCatalog.
294  \param exposure Exposure to process; DETECTED mask plane will be set in-place.
295  \param doSmooth if True, smooth the image before detection using a Gaussian of width sigma (default: True)
296  \param sigma sigma of PSF (pixels); used for smoothing and to grow detections;
297  if None then measure the sigma of the PSF of the exposure (default: None)
298  \param clearMask Clear DETECTED{,_NEGATIVE} planes before running detection (default: True)
299 
300  \return a lsst.pipe.base.Struct with:
301  - sources -- an lsst.afw.table.SourceCatalog object
302  - fpSets --- lsst.pipe.base.Struct returned by \link detectFootprints \endlink
303 
304  \throws ValueError if flags.negative is needed, but isn't in table's schema
305  \throws lsst.pipe.base.TaskError if sigma=None, doSmooth=True and the exposure has no PSF
306 
307  \note
308  If you want to avoid dealing with Sources and Tables, you can use detectFootprints()
309  to just get the afw::detection::FootprintSet%s.
310 
311  """
312  if self.negativeFlagKey is not None and self.negativeFlagKey not in table.getSchema():
313  raise ValueError("Table has incorrect Schema")
314  fpSets = self.detectFootprints(exposure=exposure, doSmooth=doSmooth, sigma=sigma,
315  clearMask=clearMask)
316  sources = afwTable.SourceCatalog(table)
317  table.preallocate(fpSets.numPos + fpSets.numNeg) # not required, but nice
318  if fpSets.negative:
319  fpSets.negative.makeSources(sources)
320  if self.negativeFlagKey:
321  for record in sources:
322  record.set(self.negativeFlagKey, True)
323  if fpSets.positive:
324  fpSets.positive.makeSources(sources)
325  return pipeBase.Struct(
326  sources = sources,
327  fpSets = fpSets
328  )
329 
330  ## An alias for run \deprecated Remove this alias after checking for where it's used
331  makeSourceCatalog = run
332 
333  @pipeBase.timeMethod
334  def detectFootprints(self, exposure, doSmooth=True, sigma=None, clearMask=True):
335  """!Detect footprints.
336 
337  \param exposure Exposure to process; DETECTED{,_NEGATIVE} mask plane will be set in-place.
338  \param doSmooth if True, smooth the image before detection using a Gaussian of width sigma
339  \param sigma sigma of PSF (pixels); used for smoothing and to grow detections;
340  if None then measure the sigma of the PSF of the exposure
341  \param clearMask Clear both DETECTED and DETECTED_NEGATIVE planes before running detection
342 
343  \return a lsst.pipe.base.Struct with fields:
344  - positive: lsst.afw.detection.FootprintSet with positive polarity footprints (may be None)
345  - negative: lsst.afw.detection.FootprintSet with negative polarity footprints (may be None)
346  - numPos: number of footprints in positive or 0 if detection polarity was negative
347  - numNeg: number of footprints in negative or 0 if detection polarity was positive
348  - background: re-estimated background. None if reEstimateBackground==False
349 
350  \throws lsst.pipe.base.TaskError if sigma=None and the exposure has no PSF
351  """
352  try:
353  import lsstDebug
354  display = lsstDebug.Info(__name__).display
355  except ImportError:
356  try:
357  display
358  except NameError:
359  display = False
360 
361  if exposure is None:
362  raise RuntimeError("No exposure for detection")
363 
364  maskedImage = exposure.getMaskedImage()
365  region = maskedImage.getBBox()
366 
367  if clearMask:
368  mask = maskedImage.getMask()
369  mask &= ~(mask.getPlaneBitMask("DETECTED") | mask.getPlaneBitMask("DETECTED_NEGATIVE"))
370  del mask
371 
372  if sigma is None:
373  psf = exposure.getPsf()
374  if psf is None:
375  raise pipeBase.TaskError("exposure has no PSF; must specify sigma")
376  shape = psf.computeShape()
377  sigma = shape.getDeterminantRadius()
378 
379  self.metadata.set("sigma", sigma)
380  self.metadata.set("doSmooth", doSmooth)
381 
382  if not doSmooth:
383  convolvedImage = maskedImage.Factory(maskedImage)
384  middle = convolvedImage
385  else:
386  # smooth using a Gaussian (which is separate, hence fast) of width sigma
387  # make a SingleGaussian (separable) kernel with the 'sigma'
388  psf = exposure.getPsf()
389  kWidth = (int(sigma * 7 + 0.5) // 2) * 2 + 1 # make sure it is odd
390  self.metadata.set("smoothingKernelWidth", kWidth)
391  gaussFunc = afwMath.GaussianFunction1D(sigma)
392  gaussKernel = afwMath.SeparableKernel(kWidth, kWidth, gaussFunc, gaussFunc)
393 
394  convolvedImage = maskedImage.Factory(maskedImage.getBBox())
395 
396  afwMath.convolve(convolvedImage, maskedImage, gaussKernel, afwMath.ConvolutionControl())
397  #
398  # Only search psf-smooth part of frame
399  #
400  goodBBox = gaussKernel.shrinkBBox(convolvedImage.getBBox())
401  middle = convolvedImage.Factory(convolvedImage, goodBBox, afwImage.PARENT, False)
402  #
403  # Mark the parts of the image outside goodBBox as EDGE
404  #
405  self.setEdgeBits(maskedImage, goodBBox, maskedImage.getMask().getPlaneBitMask("EDGE"))
406 
407  fpSets = pipeBase.Struct(positive=None, negative=None)
408 
409  if self.config.thresholdPolarity != "negative":
410  fpSets.positive = self.thresholdImage(middle, "positive")
411  if self.config.reEstimateBackground or self.config.thresholdPolarity != "positive":
412  fpSets.negative = self.thresholdImage(middle, "negative")
413 
414  for polarity, maskName in (("positive", "DETECTED"), ("negative", "DETECTED_NEGATIVE")):
415  fpSet = getattr(fpSets, polarity)
416  if fpSet is None:
417  continue
418  fpSet.setRegion(region)
419  if self.config.nSigmaToGrow > 0:
420  nGrow = int((self.config.nSigmaToGrow * sigma) + 0.5)
421  self.metadata.set("nGrow", nGrow)
422  fpSet = afwDet.FootprintSet(fpSet, nGrow, self.config.isotropicGrow)
423  fpSet.setMask(maskedImage.getMask(), maskName)
424  if not self.config.returnOriginalFootprints:
425  setattr(fpSets, polarity, fpSet)
426 
427  fpSets.numPos = len(fpSets.positive.getFootprints()) if fpSets.positive is not None else 0
428  fpSets.numNeg = len(fpSets.negative.getFootprints()) if fpSets.negative is not None else 0
429 
430  if self.config.thresholdPolarity != "negative":
431  self.log.log(self.log.INFO, "Detected %d positive sources to %g sigma." %
432  (fpSets.numPos, self.config.thresholdValue))
433 
434  fpSets.background = None
435  if self.config.reEstimateBackground:
436  mi = exposure.getMaskedImage()
437  bkgd = getBackground(mi, self.config.background)
438 
439  if self.config.adjustBackground:
440  self.log.log(self.log.WARN, "Fiddling the background by %g" % self.config.adjustBackground)
441 
442  bkgd += self.config.adjustBackground
443  fpSets.background = bkgd
444  self.log.log(self.log.INFO, "Resubtracting the background after object detection")
445  mi -= bkgd.getImageF()
446  del mi
447 
448  if self.config.thresholdPolarity == "positive":
449  if self.config.reEstimateBackground:
450  mask = maskedImage.getMask()
451  mask &= ~mask.getPlaneBitMask("DETECTED_NEGATIVE")
452  del mask
453  fpSets.negative = None
454  else:
455  self.log.log(self.log.INFO, "Detected %d negative sources to %g %s" %
456  (fpSets.numNeg, self.config.thresholdValue,
457  ("DN" if self.config.thresholdType == "value" else "sigma")))
458 
459  if display:
460  ds9.mtv(exposure, frame=0, title="detection")
461 
462  if convolvedImage and display and display > 1:
463  ds9.mtv(convolvedImage, frame=1, title="PSF smoothed")
464 
465  return fpSets
466 
467  def thresholdImage(self, image, thresholdParity, maskName="DETECTED"):
468  """!Threshold the convolved image, returning a FootprintSet.
469  Helper function for detect().
470 
471  \param image The (optionally convolved) MaskedImage to threshold
472  \param thresholdParity Parity of threshold
473  \param maskName Name of mask to set
474 
475  \return FootprintSet
476  """
477  parity = False if thresholdParity == "negative" else True
478  threshold = afwDet.createThreshold(self.config.thresholdValue, self.config.thresholdType, parity)
479  threshold.setIncludeMultiplier(self.config.includeThresholdMultiplier)
480  fpSet = afwDet.FootprintSet(image, threshold, maskName, self.config.minPixels)
481  return fpSet
482 
483  @staticmethod
484  def setEdgeBits(maskedImage, goodBBox, edgeBitmask):
485  """!Set the edgeBitmask bits for all of maskedImage outside goodBBox
486 
487  \param[in,out] maskedImage image on which to set edge bits in the mask
488  \param[in] goodBBox bounding box of good pixels, in LOCAL coordinates
489  \param[in] edgeBitmask bit mask to OR with the existing mask bits in the region outside goodBBox
490  """
491  msk = maskedImage.getMask()
492 
493  mx0, my0 = maskedImage.getXY0()
494  for x0, y0, w, h in ([0, 0,
495  msk.getWidth(), goodBBox.getBeginY() - my0],
496  [0, goodBBox.getEndY() - my0, msk.getWidth(),
497  maskedImage.getHeight() - (goodBBox.getEndY() - my0)],
498  [0, 0,
499  goodBBox.getBeginX() - mx0, msk.getHeight()],
500  [goodBBox.getEndX() - mx0, 0,
501  maskedImage.getWidth() - (goodBBox.getEndX() - mx0), msk.getHeight()],
502  ):
503  edgeMask = msk.Factory(msk, afwGeom.BoxI(afwGeom.PointI(x0, y0),
504  afwGeom.ExtentI(w, h)), afwImage.LOCAL)
505  edgeMask |= edgeBitmask
506 
507 def addExposures(exposureList):
508  """!Add a set of exposures together.
509 
510  \param[in] exposureList sequence of exposures to add
511 
512  \return an exposure of the same size as each exposure in exposureList,
513  with the metadata from exposureList[0] and a masked image equal to the
514  sum of all the exposure's masked images.
515 
516  \throw LsstException if the exposures do not all have the same dimensions (but does not check xy0)
517  """
518  exposure0 = exposureList[0]
519  image0 = exposure0.getMaskedImage()
520 
521  addedImage = image0.Factory(image0, True)
522  addedImage.setXY0(image0.getXY0())
523 
524  for exposure in exposureList[1:]:
525  image = exposure.getMaskedImage()
526  addedImage += image
527 
528  addedExposure = exposure0.Factory(addedImage, exposure0.getWcs())
529  return addedExposure
530 
531 def getBackground(image, backgroundConfig, nx=0, ny=0, algorithm=None):
532  """!Estimate the background of an image (a thin layer on lsst.afw.math.makeBackground)
533 
534  \param[in] image image whose background is to be computed
535  \param[in] backgroundConfig configuration (a BackgroundConfig)
536  \param[in] nx number of x bands; 0 for default
537  \param[in] ny number of y bands; 0 for default
538  \param[in] algorithm name of interpolation algorithm; see lsst.afw.math.BackgroundControl for details
539  """
540  backgroundConfig.validate();
541 
542  if not nx:
543  nx = image.getWidth()//backgroundConfig.binSize + 1
544  if not ny:
545  ny = image.getHeight()//backgroundConfig.binSize + 1
546 
547  sctrl = afwMath.StatisticsControl()
548  sctrl.setAndMask(reduce(lambda x, y: x | image.getMask().getPlaneBitMask(y),
549  backgroundConfig.ignoredPixelMask, 0x0))
550  sctrl.setNanSafe(backgroundConfig.isNanSafe)
551 
552  pl = pexLogging.Debug("meas.utils.sourceDetection.getBackground")
553  pl.debug(3, "Ignoring mask planes: %s" % ", ".join(backgroundConfig.ignoredPixelMask))
554 
555  if not algorithm:
556  algorithm = backgroundConfig.algorithm
557 
558  bctrl = afwMath.BackgroundControl(algorithm, nx, ny,
559  backgroundConfig.undersampleStyle, sctrl,
560  backgroundConfig.statisticsProperty)
561 
562  return afwMath.makeBackground(image, bctrl)
563 
564 getBackground.ConfigClass = BackgroundConfig
565 
566 def estimateBackground(exposure, backgroundConfig, subtract=True, stats=True,
567  statsKeys=None):
568  """!Estimate exposure's background using parameters in backgroundConfig.
569 
570  If subtract is true, make a copy of the exposure and subtract the background.
571  If `stats` is True, measure the mean and variance of the background and
572  add them to the background-subtracted exposure's metadata with keys
573  "BGMEAN" and "BGVAR", or the keys given in `statsKeys` (2-tuple of strings).
574 
575  Return background, backgroundSubtractedExposure
576  """
577  displayBackground = lsstDebug.Info(__name__).displayBackground
578 
579  maskedImage = exposure.getMaskedImage()
580 
581  background = getBackground(maskedImage, backgroundConfig)
582 
583  if not background:
584  raise RuntimeError, "Unable to estimate background for exposure"
585 
586  bgimg = None
587 
588  if displayBackground > 1:
589  bgimg = background.getImageF()
590  ds9.mtv(bgimg, title="background", frame=1)
591 
592  if not subtract:
593  return background, None
594 
595  bbox = maskedImage.getBBox()
596  backgroundSubtractedExposure = exposure.Factory(exposure, bbox, afwImage.PARENT, True)
597  copyImage = backgroundSubtractedExposure.getMaskedImage().getImage()
598  if bgimg is None:
599  bgimg = background.getImageF()
600  copyImage -= bgimg
601 
602  # Record statistics of the background in the bgsub exposure metadata.
603  # (lsst.daf.base.PropertySet)
604  if stats:
605  if statsKeys is None:
606  mnkey = 'BGMEAN'
607  varkey = 'BGVAR'
608  else:
609  mnkey,varkey = statsKeys
610  meta = backgroundSubtractedExposure.getMetadata()
611  s = afwMath.makeStatistics(bgimg, afwMath.MEAN | afwMath.VARIANCE)
612  bgmean = s.getValue(afwMath.MEAN)
613  bgvar = s.getValue(afwMath.VARIANCE)
614  meta.addDouble(mnkey, bgmean)
615  meta.addDouble(varkey, bgvar)
616 
617  if displayBackground:
618  ds9.mtv(backgroundSubtractedExposure, title="subtracted")
619 
620  return background, backgroundSubtractedExposure
621 estimateBackground.ConfigClass = BackgroundConfig
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:446
def addExposures
Add a set of exposures together.
Definition: detection.py:507
Parameters to control convolution.
Definition: ConvolveImage.h:58
Config for background estimation.
Definition: detection.py:38
A kernel described by a pair of functions: func(x, y) = colFunc(x) * rowFunc(y)
Definition: Kernel.h:986
def setEdgeBits
Set the edgeBitmask bits for all of maskedImage outside goodBBox.
Definition: detection.py:484
Detect positive and negative sources on an exposure and return a new table.SourceCatalog.
Definition: detection.py:158
Pass parameters to a Background object.
Definition: Background.h:62
An integer coordinate rectangle.
Definition: Box.h:53
Pass parameters to a Statistics objectA class to pass parameters which control how the stats are calc...
Definition: Statistics.h:92
def run
Run source detection and create a SourceCatalog.
Definition: detection.py:290
Configuration parameters for the SourceDetectionTask.
Definition: detection.py:90
Custom catalog class for record/table subclasses that are guaranteed to have an ID, and should generally be sorted by that ID.
Definition: fwd.h:55
def estimateBackground
Estimate exposure&#39;s background using parameters in backgroundConfig.
Definition: detection.py:567
Threshold createThreshold(const double value, const std::string type="value", const bool polarity=true)
Factory method for creating Threshold objects.
Definition: Threshold.cc:138
def thresholdImage
Threshold the convolved image, returning a FootprintSet.
Definition: detection.py:467
A set of Footprints, associated with a MaskedImage.
Definition: FootprintSet.h:53
Statistics makeStatistics(afwImage::Mask< afwImage::MaskPixel > const &msk, int const flags, StatisticsControl const &sctrl)
Specialization to handle Masks.
Definition: Statistics.cc:1023
void convolve(OutImageT &convolvedImage, InImageT const &inImage, KernelT const &kernel, bool doNormalize, bool doCopyEdge=false)
Old, deprecated version of convolve.
def getBackground
Estimate the background of an image (a thin layer on lsst.afw.math.makeBackground) ...
Definition: detection.py:531