LSST Applications  21.0.0-172-gfb10e10a+18fedfabac,22.0.0+297cba6710,22.0.0+80564b0ff1,22.0.0+8d77f4f51a,22.0.0+a28f4c53b1,22.0.0+dcf3732eb2,22.0.1-1-g7d6de66+2a20fdde0d,22.0.1-1-g8e32f31+297cba6710,22.0.1-1-geca5380+7fa3b7d9b6,22.0.1-12-g44dc1dc+2a20fdde0d,22.0.1-15-g6a90155+515f58c32b,22.0.1-16-g9282f48+790f5f2caa,22.0.1-2-g92698f7+dcf3732eb2,22.0.1-2-ga9b0f51+7fa3b7d9b6,22.0.1-2-gd1925c9+bf4f0e694f,22.0.1-24-g1ad7a390+a9625a72a8,22.0.1-25-g5bf6245+3ad8ecd50b,22.0.1-25-gb120d7b+8b5510f75f,22.0.1-27-g97737f7+2a20fdde0d,22.0.1-32-gf62ce7b1+aa4237961e,22.0.1-4-g0b3f228+2a20fdde0d,22.0.1-4-g243d05b+871c1b8305,22.0.1-4-g3a563be+32dcf1063f,22.0.1-4-g44f2e3d+9e4ab0f4fa,22.0.1-42-gca6935d93+ba5e5ca3eb,22.0.1-5-g15c806e+85460ae5f3,22.0.1-5-g58711c4+611d128589,22.0.1-5-g75bb458+99c117b92f,22.0.1-6-g1c63a23+7fa3b7d9b6,22.0.1-6-g50866e6+84ff5a128b,22.0.1-6-g8d3140d+720564cf76,22.0.1-6-gd805d02+cc5644f571,22.0.1-8-ge5750ce+85460ae5f3,master-g6e05de7fdc+babf819c66,master-g99da0e417a+8d77f4f51a,w.2021.48
LSST Data Management Base Package
baseline.py
Go to the documentation of this file.
1 # This file is part of meas_deblender.
2 #
3 # Developed for the LSST Data Management System.
4 # This product includes software developed by the LSST Project
5 # (https://www.lsst.org).
6 # See the COPYRIGHT file at the top-level directory of this distribution
7 # for details of code ownership.
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 GNU General Public License
20 # along with this program. If not, see <https://www.gnu.org/licenses/>.
21 
22 __all__ = ["DEFAULT_PLUGINS", "DeblenderResult", "DeblendedParent", "MultiColorPeak",
23  "DeblendedPeak", "deblend", "newDeblend", "CachingPsf"]
24 
25 from collections import OrderedDict
26 import numpy as np
27 
29 import lsst.afw.image as afwImage
30 import lsst.afw.detection as afwDet
31 import lsst.geom as geom
32 import lsst.afw.math as afwMath
33 
34 from . import plugins
35 
36 DEFAULT_PLUGINS = [
37  plugins.DeblenderPlugin(plugins.fitPsfs),
38  plugins.DeblenderPlugin(plugins.buildSymmetricTemplates),
39  plugins.DeblenderPlugin(plugins.medianSmoothTemplates),
40  plugins.DeblenderPlugin(plugins.makeTemplatesMonotonic),
41  plugins.DeblenderPlugin(plugins.clipFootprintsToNonzero),
42  plugins.DeblenderPlugin(plugins.reconstructTemplates, onReset=5),
43  plugins.DeblenderPlugin(plugins.apportionFlux),
44 ]
45 
46 
48  r"""Collection of objects in multiple bands for a single parent footprint.
49 
50  Parameters
51  ----------
52  footprint: `afw.detection.Footprint`
53  Parent footprint to deblend. While `maskedImages`, `psfs`,
54  and `psffwhms` are lists of objects, one for each band,
55  `footprint` is a single parent footprint (from a `mergedDet`)
56  this is used for all bands.
57  mMaskedImage: `MaskedImage`\ s or `MultibandMaskedImage`
58  Masked image containing the ``footprint`` in each band.
59  psfs: list of `afw.detection.Psf`s
60  Psf of the ``maskedImage`` for each band.
61  psffwhm: list of `float`s
62  FWHM of the ``maskedImage``'s ``psf`` in each band.
63  maxNumberOfPeaks: `int`, optional
64  If positive, the maximum number of peaks to deblend.
65  If the total number of peaks is greater than ``maxNumberOfPeaks``,
66  then only the first ``maxNumberOfPeaks`` sources are deblended.
67  The default is 0, which deblends all of the peaks.
68  avgNoise: `float`or list of `float`s, optional
69  Average noise level in each ``maskedImage``.
70  The default is ``None``, which estimates the noise from the median value of the
71  variance plane of ``maskedImage`` for each filter.
72  """
73 
74  def __init__(self, footprint, mMaskedImage, psfs, psffwhms, log,
75  maxNumberOfPeaks=0, avgNoise=None):
76  # Check if this is collection of footprints in multiple bands or a single footprint
77  if not isinstance(mMaskedImage, afwImage.MultibandMaskedImage):
78  mMaskedImage = [mMaskedImage]
79  self.filtersfilters = [0]
80  else:
81  self.filtersfilters = mMaskedImage.filters
82  try:
83  len(psfs)
84  except TypeError:
85  psfs = [psfs]
86  try:
87  len(psffwhms)
88  except TypeError:
89  psffwhms = [psffwhms]
90  try:
91  len(avgNoise)
92  except TypeError:
93  if avgNoise is None:
94  avgNoise = [None]*len(psfs)
95  else:
96  avgNoise = [avgNoise]
97  # Now check that all of the parameters have the same number of entries
98  if any([len(self.filtersfilters) != len(p) for p in [psfs, psffwhms, avgNoise]]):
99  raise ValueError("To use the multi-color deblender, "
100  "'maskedImage', 'psf', 'psffwhm', 'avgNoise'"
101  "must have the same length, but instead have lengths: "
102  "{0}".format([len(p) for p in [mMaskedImage,
103  psfs,
104  psffwhms,
105  avgNoise]]))
106 
107  self.loglog = log
108  self.mMaskedImagemMaskedImage = mMaskedImage
109  self.footprintfootprint = footprint
110  self.psfspsfs = psfs
111 
112  self.peakCountpeakCount = len(footprint.getPeaks())
113  if maxNumberOfPeaks > 0 and maxNumberOfPeaks < self.peakCountpeakCount:
114  self.peakCountpeakCount = maxNumberOfPeaks
115 
116  # Create a DeblendedParent for the Footprint in every filter
117  self.deblendedParentsdeblendedParents = OrderedDict([])
118  for n, f in enumerate(self.filtersfilters):
119  f = self.filtersfilters[n]
120  dp = DeblendedParent(f, footprint, mMaskedImage[f], psfs[n],
121  psffwhms[n], avgNoise[n], maxNumberOfPeaks, self)
122  self.deblendedParentsdeblendedParents[self.filtersfilters[n]] = dp
123 
124  # Group the peaks in each color
125  self.peakspeaks = []
126  for idx in range(self.peakCountpeakCount):
127  peakDict = OrderedDict([(f, dp.peaks[idx]) for f, dp in self.deblendedParentsdeblendedParents.items()])
128  multiPeak = MultiColorPeak(peakDict, idx, self)
129  self.peakspeaks.append(multiPeak)
130 
131  # Result from multiband debender (if used)
132  self.blendblend = None
133  self.failedfailed = False
134 
135  def getParentProperty(self, propertyName):
136  """Get the footprint in each filter
137  """
138  return [getattr(dp, propertyName) for dp in self.deblendedParentsdeblendedParents]
139 
140  def setTemplateSums(self, templateSums, fidx=None):
141  if fidx is not None:
142  self.templateSums[fidx] = templateSums
143  else:
144  for f, templateSum in templateSums.items():
145  self.deblendedParentsdeblendedParents[f].templateSum = templateSum
146 
147 
149  """Deblender result of a single parent footprint, in a single band
150 
151  Convenience class to store useful objects used by the deblender for a single band,
152  such as the maskedImage, psf, etc as well as the results from the deblender.
153 
154  Parameters
155  ----------
156  filterName: `str`
157  Name of the filter used for `maskedImage`
158  footprint: list of `afw.detection.Footprint`s
159  Parent footprint to deblend in each band.
160  maskedImages: list of `afw.image.MaskedImageF`s
161  Masked image containing the ``footprint`` in each band.
162  psf: list of `afw.detection.Psf`s
163  Psf of the ``maskedImage`` for each band.
164  psffwhm: list of `float`s
165  FWHM of the ``maskedImage``'s ``psf`` in each band.
166  avgNoise: `float`or list of `float`s, optional
167  Average noise level in each ``maskedImage``.
168  The default is ``None``, which estimates the noise from the median value of the
169  variance plane of ``maskedImage`` for each filter.
170  maxNumberOfPeaks: `int`, optional
171  If positive, the maximum number of peaks to deblend.
172  If the total number of peaks is greater than ``maxNumberOfPeaks``,
173  then only the first ``maxNumberOfPeaks`` sources are deblended.
174  The default is 0, which deblends all of the peaks.
175  debResult: `DeblenderResult`
176  The ``DeblenderResult`` that contains this ``DeblendedParent``.
177  """
178  def __init__(self, filterName, footprint, maskedImage, psf, psffwhm, avgNoise,
179  maxNumberOfPeaks, debResult):
180  self.filterfilter = filterName
181  self.fpfp = footprint
182  self.maskedImagemaskedImage = maskedImage
183  self.psfpsf = psf
184  self.psffwhmpsffwhm = psffwhm
185  self.imgimg = maskedImage.getImage()
186  self.imbbimbb = self.imgimg.getBBox()
187  self.varimgvarimg = maskedImage.getVariance()
188  self.maskmask = maskedImage.getMask()
189  self.avgNoiseavgNoise = avgNoise
190  self.updateFootprintBboxupdateFootprintBbox()
191  self.debResultdebResult = debResult
192  self.peakCountpeakCount = debResult.peakCount
193  self.templateSumtemplateSum = None
194 
195  # avgNoise is an estiamte of the average noise level for the image in this filter
196  if avgNoise is None:
197  stats = afwMath.makeStatistics(self.varimgvarimg, self.maskmask, afwMath.MEDIAN)
198  avgNoise = np.sqrt(stats.getValue(afwMath.MEDIAN))
199  debResult.log.trace('Estimated avgNoise for filter %s = %f', self.filterfilter, avgNoise)
200  self.avgNoiseavgNoise = avgNoise
201 
202  # Store all of the peak information in a single object
203  self.peakspeaks = []
204  peaks = self.fpfp.getPeaks()
205  for idx in range(self.peakCountpeakCount):
206  deblendedPeak = DeblendedPeak(peaks[idx], idx, self)
207  self.peakspeaks.append(deblendedPeak)
208 
210  """Update the bounding box of the parent footprint
211 
212  If the parent Footprint is resized it will be necessary to update the bounding box of the footprint.
213  """
214  # Pull out the image bounds of the parent Footprint
215  self.bbbb = self.fpfp.getBBox()
216  if not self.imbbimbb.contains(self.bbbb):
217  raise ValueError(('Footprint bounding-box %s extends outside image bounding-box %s') %
218  (str(self.bbbb), str(self.imbbimbb)))
219  self.W, self.HH = self.bbbb.getWidth(), self.bbbb.getHeight()
220  self.x0, self.y0y0 = self.bbbb.getMinX(), self.bbbb.getMinY()
221  self.x1, self.y1y1 = self.bbbb.getMaxX(), self.bbbb.getMaxY()
222 
223 
225  """Collection of single peak deblender results in multiple bands.
226 
227  There is one of these objects for each Peak in the footprint.
228 
229  Parameters
230  ----------
231  peaks: `dict` of `afw.detection.PeakRecord`
232  Each entry in ``peaks`` is a peak record for the same peak in a different band
233  pki: int
234  Index of the peak in `parent.peaks`
235  parent: `DeblendedParent`
236  DeblendedParent object that contains the peak as a child.
237  """
238  def __init__(self, peaks, pki, parent):
239  self.filtersfilters = list(peaks.keys())
240  self.deblendedPeaksdeblendedPeaks = peaks
241  self.parentparent = parent
242  for filter, peak in self.deblendedPeaksdeblendedPeaks.items():
243  peak.multiColorPeak = self
244 
245  # Fields common to the peak in all bands that will be set by the deblender
246  # In the future this is likely to contain information about the probability of the peak
247  # being a point source, color-information about templateFootprints, etc.
248  self.pkipki = pki
249  self.skipskip = False
250  self.deblendedAsPsfdeblendedAsPsf = False
251  self.xx = self.deblendedPeaksdeblendedPeaks[self.filtersfilters[0]].peak.getFx()
252  self.yy = self.deblendedPeaksdeblendedPeaks[self.filtersfilters[0]].peak.getFy()
253 
254 
256  """Result of deblending a single Peak within a parent Footprint.
257 
258  There is one of these objects for each Peak in the Footprint.
259 
260  Parameters
261  ----------
262  peak: `afw.detection.PeakRecord`
263  Peak object in a single band from a peak record
264  pki: `int`
265  Index of the peak in `multiColorPeak.parent.peaks`
266  parent: `DeblendedParent`
267  Parent in the same filter that contains the peak
268  multiColorPeak: `MultiColorPeak`
269  Object containing the same peak in multiple bands
270  """
271  def __init__(self, peak, pki, parent, multiColorPeak=None):
272  # Peak object
273  self.peakpeak = peak
274  # int, peak index number
275  self.pkipki = pki
276  self.parentparent = parent
277  self.multiColorPeakmultiColorPeak = multiColorPeak
278  # union of all the ways of failing...
279  self.skipskip = False
280 
281  self.outOfBoundsoutOfBounds = False
282  self.tinyFootprinttinyFootprint = False
283  self.noValidPixelsnoValidPixels = False
284  self.deblendedAsPsfdeblendedAsPsf = False
285  self.degeneratedegenerate = False
286 
287  # Field set during _fitPsf:
288  self.psfFitFailedpsfFitFailed = False
289  self.psfFitBadDofpsfFitBadDof = False
290  # (chisq, dof) for PSF fit without decenter
291  self.psfFit1psfFit1 = None
292  # (chisq, dof) for PSF fit with decenter
293  self.psfFit2psfFit2 = None
294  # (chisq, dof) for PSF fit after applying decenter
295  self.psfFit3psfFit3 = None
296  # decentered PSF fit wanted to move the center too much
297  self.psfFitBigDecenterpsfFitBigDecenter = False
298  # was the fit with decenter better?
299  self.psfFitWithDecenterpsfFitWithDecenter = False
300  #
301  self.psfFitR0psfFitR0 = None
302  self.psfFitR1psfFitR1 = None
303  self.psfFitStampExtentpsfFitStampExtent = None
304  self.psfFitCenterpsfFitCenter = None
305  self.psfFitBestpsfFitBest = None
306  self.psfFitParamspsfFitParams = None
307  self.psfFitFluxpsfFitFlux = None
308  self.psfFitNOtherspsfFitNOthers = None
309 
310  # Things only set in _fitPsf when debugging is turned on:
311  self.psfFitDebugPsf0ImgpsfFitDebugPsf0Img = None
312  self.psfFitDebugPsfImgpsfFitDebugPsfImg = None
313  self.psfFitDebugPsfDerivImgpsfFitDebugPsfDerivImg = None
314  self.psfFitDebugPsfModelpsfFitDebugPsfModel = None
315 
316  self.failedSymmetricTemplatefailedSymmetricTemplate = False
317 
318  # The actual template Image and Footprint
319  self.templateImagetemplateImage = None
320  self.templateFootprinttemplateFootprint = None
321 
322  # The flux assigned to this template -- a MaskedImage
323  self.fluxPortionfluxPortion = None
324 
325  # The stray flux assigned to this template (may be None), a HeavyFootprint
326  self.strayFluxstrayFlux = None
327 
328  self.hasRampedTemplatehasRampedTemplate = False
329 
330  self.patchedpatched = False
331 
332  # debug -- a copy of the original symmetric template
333  self.origTemplateorigTemplate = None
334  self.origFootprintorigFootprint = None
335  # MaskedImage
336  self.rampedTemplaterampedTemplate = None
337  # MaskedImage
338  self.medianFilteredTemplatemedianFilteredTemplate = None
339 
340  # when least-squares fitting templates, the template weight.
341  self.templateWeighttemplateWeight = 1.0
342 
343  def __str__(self):
344  return (('deblend result: outOfBounds: %s, deblendedAsPsf: %s') %
345  (self.outOfBoundsoutOfBounds, self.deblendedAsPsfdeblendedAsPsf))
346 
347  @property
348  def psfFitChisq(self):
349  chisq, dof = self.psfFitBestpsfFitBest
350  return chisq
351 
352  @property
353  def psfFitDof(self):
354  chisq, dof = self.psfFitBestpsfFitBest
355  return dof
356 
357  def getFluxPortion(self, strayFlux=True):
358  """
359  Return a HeavyFootprint containing the flux apportioned to this peak.
360 
361  @param[in] strayFlux include stray flux also?
362  """
363  if self.templateFootprinttemplateFootprint is None or self.fluxPortionfluxPortion is None:
364  return None
365  heavy = afwDet.makeHeavyFootprint(self.templateFootprinttemplateFootprint, self.fluxPortionfluxPortion)
366  if strayFlux:
367  if self.strayFluxstrayFlux is not None:
368  heavy = afwDet.mergeHeavyFootprints(heavy, self.strayFluxstrayFlux)
369 
370  return heavy
371 
372  def setStrayFlux(self, stray):
373  self.strayFluxstrayFlux = stray
374 
375  def setFluxPortion(self, mimg):
376  self.fluxPortionfluxPortion = mimg
377 
378  def setTemplateWeight(self, w):
379  self.templateWeighttemplateWeight = w
380 
381  def setPatched(self):
382  self.patchedpatched = True
383 
384  # DEBUG
385  def setOrigTemplate(self, t, tfoot):
386  self.origTemplateorigTemplate = t.Factory(t, True)
387  self.origFootprintorigFootprint = tfoot
388 
389  def setRampedTemplate(self, t, tfoot):
390  self.hasRampedTemplatehasRampedTemplate = True
391  self.rampedTemplaterampedTemplate = t.Factory(t, True)
392 
393  def setMedianFilteredTemplate(self, t, tfoot):
394  self.medianFilteredTemplatemedianFilteredTemplate = t.Factory(t, True)
395 
396  def setPsfTemplate(self, tim, tfoot):
397  self.psfFootprintpsfFootprint = afwDet.Footprint(tfoot)
398  self.psfTemplatepsfTemplate = tim.Factory(tim, True)
399 
400  def setOutOfBounds(self):
401  self.outOfBoundsoutOfBounds = True
402  self.skipskip = True
403 
404  def setTinyFootprint(self):
405  self.tinyFootprinttinyFootprint = True
406  self.skipskip = True
407 
408  def setNoValidPixels(self):
409  self.noValidPixelsnoValidPixels = True
410  self.skipskip = True
411 
412  def setPsfFitFailed(self):
413  self.psfFitFailedpsfFitFailed = True
414 
415  def setBadPsfDof(self):
416  self.psfFitBadDofpsfFitBadDof = True
417 
418  def setDeblendedAsPsf(self):
419  self.deblendedAsPsfdeblendedAsPsf = True
420 
422  self.failedSymmetricTemplatefailedSymmetricTemplate = True
423  self.skipskip = True
424 
425  def setTemplate(self, image, footprint):
426  self.templateImagetemplateImage = image
427  self.templateFootprinttemplateFootprint = footprint
428 
429 
430 def deblend(footprint, maskedImage, psf, psffwhm,
431  psfChisqCut1=1.5, psfChisqCut2=1.5, psfChisqCut2b=1.5, fitPsfs=True,
432  medianSmoothTemplate=True, medianFilterHalfsize=2,
433  monotonicTemplate=True, weightTemplates=False,
434  log=None, verbose=False, sigma1=None, maxNumberOfPeaks=0,
435  assignStrayFlux=True, strayFluxToPointSources='necessary', strayFluxAssignment='r-to-peak',
436  rampFluxAtEdge=False, patchEdges=False, tinyFootprintSize=2,
437  getTemplateSum=False, clipStrayFluxFraction=0.001, clipFootprintToNonzero=True,
438  removeDegenerateTemplates=False, maxTempDotProd=0.5
439  ):
440  r"""Deblend a parent ``Footprint`` in a ``MaskedImageF``.
441 
442  Deblending assumes that ``footprint`` has multiple peaks, as it will still create a
443  `PerFootprint` object with a list of peaks even if there is only one peak in the list.
444  It is recommended to first check that ``footprint`` has more than one peak, similar to the
445  execution of `lsst.meas.deblender.deblend.SourceDeblendTask`.
446 
447  .. note::
448  This is the API for the old deblender, however the function builds the plugins necessary
449  to use the new deblender to perform identically to the old deblender.
450  To test out newer functionality use ``newDeblend`` instead.
451 
452  Deblending involves several mandatory and optional steps:
453 
454  # Optional: If ``fitPsfs`` is True, find all peaks that are well-fit by a PSF + background model
455 
456  * Peaks that pass the cuts have their footprints modified to the PSF + background model
457  and their ``deblendedAsPsf`` property set to ``True``.
458  * Relevant parameters: ``psfChisqCut1``, ``psfChisqCut2``, ``psfChisqCut2b``,
459  ``tinyFootprintSize``.
460  * See the parameter descriptions for more.
461 
462  # Build a symmetric template for each peak not well-fit by the PSF model
463 
464  * Given ``maskedImageF``, ``footprint``, and a ``DeblendedPeak``, creates a symmetric
465  template (``templateImage`` and ``templateFootprint``) around the peak
466  for all peaks not flagged as ``skip`` or ``deblendedAsPsf``.
467  * If ``patchEdges=True`` and if ``footprint`` touches pixels with the
468  ``EDGE`` bit set, then ``footprint`` is grown to include spans whose
469  symmetric mirror is outside of the image.
470  * Relevant parameters: ``sigma1`` and ``patchEdges``.
471 
472  # Optional: If ``rampFluxAtEdge`` is True, adjust flux on the edges of the template footprints
473 
474  * Using the PSF, a peak ``Footprint`` with pixels on the edge of of ``footprint``
475  is grown by the psffwhm*1.5 and filled in with zeros.
476  * The result is a new symmetric footprint template for the peaks near the edge.
477  * Relevant parameter: ``patchEdges``.
478 
479  # Optionally (``medianSmoothTemplate=True``) filter the template images
480 
481  * Apply a median smoothing filter to all of the template images.
482  * Relevant parameters: ``medianFilterHalfSize``
483 
484  # Optional: If ``monotonicTemplate`` is True, make the templates monotonic.
485 
486  * The pixels in the templates are modified such that pixels
487  further from the peak will have values smaller than those closer to the peak.
488 
489  # Optional: If ``clipFootprintToNonzero`` is True, clip non-zero spans in the template footprints
490 
491  * Peak ``Footprint``\ s are clipped to the region in the image containing non-zero values
492  by dropping spans that are completely zero and moving endpoints to non-zero pixels
493  (but does not split spans that have internal zeros).
494 
495  # Optional: If ``weightTemplates`` is True, weight the templates to best fit the observed image
496 
497  * Re-weight the templates so that their linear combination
498  best represents the observed ``maskedImage``
499 
500  # Optional: If ``removeDegenerateTempaltes`` is True, reconstruct shredded galaxies
501 
502  * If galaxies have substructure, such as face-on spirals, the process of identifying peaks can
503  "shred" the galaxy into many pieces. The templates of shredded galaxies are typically quite
504  similar because they represent the same galaxy, so we try to identify these "degenerate" peaks
505  by looking at the inner product (in pixel space) of pairs of templates.
506  * If they are nearly parallel, we only keep one of the peaks and reject the other.
507  * If only one of the peaks is a PSF template, the other template is used,
508  otherwise the one with the maximum template value is kept.
509  * Relevant parameters: ``maxTempDotProduct``
510 
511  # Apportion flux to all of the peak templates
512 
513  * Divide the ``maskedImage`` flux amongst all of the templates based on the fraction of
514  flux assigned to each ``tempalteFootprint``.
515  * Leftover "stray flux" is assigned to peaks based on the other parameters.
516  * Relevant parameters: ``clipStrayFluxFraction``, ``strayFluxAssignment``,
517  ``strayFluxToPointSources``, ``assignStrayFlux``
518 
519  Parameters
520  ----------
521  footprint: `afw.detection.Footprint`
522  Parent footprint to deblend
523  maskedImage: `afw.image.MaskedImageF`
524  Masked image containing the ``footprint``
525  psf: `afw.detection.Psf`
526  Psf of the ``maskedImage``
527  psffwhm: `float`
528  FWHM of the ``maskedImage``\'s ``psf``
529  psfChisqCut*: `float`, optional
530  If ``fitPsfs==True``, all of the peaks are fit to the image PSF.
531  ``psfChisqCut1`` is the maximum chi-squared-per-degree-of-freedom allowed for a peak to
532  be considered a PSF match without recentering.
533  A fit is also made that includes terms to recenter the PSF.
534  ``psfChisqCut2`` is the same as ``psfChisqCut1`` except it determines the restriction on the
535  fit that includes recentering terms.
536  If the peak is a match for a re-centered PSF, the PSF is repositioned at the new center and
537  the peak footprint is fit again, this time to the new PSF.
538  If the resulting chi-squared-per-degree-of-freedom is less than ``psfChisqCut2b`` then it
539  passes the re-centering algorithm.
540  If the peak passes both the re-centered and fixed position cuts, the better of the two is accepted,
541  but parameters for all three psf fits are stored in the ``DeblendedPeak``.
542  The default for ``psfChisqCut1``, ``psfChisqCut2``, and ``psfChisqCut2b`` is ``1.5``.
543  fitPsfs: `bool`, optional
544  If True then all of the peaks will be compared to the image PSF to
545  distinguish stars from galaxies.
546  medianSmoothTemplate: ``bool``, optional
547  If ``medianSmoothTemplate==True`` it a median smoothing filter is applied to the ``maskedImage``.
548  The default is ``True``.
549  medianFilterHalfSize: `int`, optional
550  Half the box size of the median filter, ie a ``medianFilterHalfSize`` of 50 means that
551  each output pixel will be the median of the pixels in a 101 x 101-pixel box in the input image.
552  This parameter is only used when ``medianSmoothTemplate==True``, otherwise it is ignored.
553  The default value is 2.
554  monotonicTempalte: `bool`, optional
555  If True then make the template monotonic.
556  The default is True.
557  weightTemplates: `bool`, optional
558  If True, re-weight the templates so that their linear combination best represents
559  the observed ``maskedImage``.
560  The default is False.
561  log: `log.Log`, optional
562  LSST logger for logging purposes.
563  The default is ``None`` (no logging).
564  verbose: `bool`, optional
565  Whether or not to show a more verbose output.
566  The default is ``False``.
567  sigma1: `float`, optional
568  Average noise level in ``maskedImage``.
569  The default is ``None``, which estimates the noise from the median value of ``maskedImage``.
570  maxNumberOfPeaks: `int`, optional
571  If nonzero, the maximum number of peaks to deblend.
572  If the total number of peaks is greater than ``maxNumberOfPeaks``,
573  then only the first ``maxNumberOfPeaks`` sources are deblended.
574  The default is 0, which deblends all of the peaks.
575  assignStrayFlux: `bool`, optional
576  If True then flux in the parent footprint that is not covered by any of the
577  template footprints is assigned to templates based on their 1/(1+r^2) distance.
578  How the flux is apportioned is determined by ``strayFluxAssignment``.
579  The default is True.
580  strayFluxToPointSources: `string`
581  Determines how stray flux is apportioned to point sources
582 
583  * ``never``: never apportion stray flux to point sources
584  * ``necessary`` (default): point sources are included only if there are no extended sources nearby
585  * ``always``: point sources are always included in the 1/(1+r^2) splitting
586 
587  strayFluxAssignment: `string`, optional
588  Determines how stray flux is apportioned.
589 
590  * ``trim``: Trim stray flux and do not include in any footprints
591  * ``r-to-peak`` (default): Stray flux is assigned based on (1/(1+r^2) from the peaks
592  * ``r-to-footprint``: Stray flux is distributed to the footprints based on 1/(1+r^2) of the
593  minimum distance from the stray flux to footprint
594  * ``nearest-footprint``: Stray flux is assigned to the footprint with lowest L-1 (Manhattan)
595  distance to the stray flux
596 
597  rampFluxAtEdge: `bool`, optional
598  If True then extend footprints with excessive flux on the edges as described above.
599  The default is False.
600  patchEdges: `bool`, optional
601  If True and if the footprint touches pixels with the ``EDGE`` bit set,
602  then grow the footprint to include all symmetric templates.
603  The default is ``False``.
604  tinyFootprintSize: `float`, optional
605  The PSF model is shrunk to the size that contains the original footprint.
606  If the bbox of the clipped PSF model for a peak is smaller than ``max(tinyFootprintSize,2)``
607  then ``tinyFootprint`` for the peak is set to ``True`` and the peak is not fit.
608  The default is 2.
609  getTemplateSum: `bool`, optional
610  As part of the flux calculation, the sum of the templates is calculated.
611  If ``getTemplateSum==True`` then the sum of the templates is stored in the result (a `PerFootprint`).
612  The default is False.
613  clipStrayFluxFraction: `float`, optional
614  Minimum stray-flux portion.
615  Any stray-flux portion less than ``clipStrayFluxFraction`` is clipped to zero.
616  The default is 0.001.
617  clipFootprintToNonzero: `bool`, optional
618  If True then clip non-zero spans in the template footprints. See above for more.
619  The default is True.
620  removeDegenerateTemplates: `bool`, optional
621  If True then we try to identify "degenerate" peaks by looking at the inner product
622  (in pixel space) of pairs of templates.
623  The default is False.
624  maxTempDotProduct: `float`, optional
625  All dot products between templates greater than ``maxTempDotProduct`` will result in one
626  of the templates removed. This parameter is only used when ``removeDegenerateTempaltes==True``.
627  The default is 0.5.
628 
629  Returns
630  -------
631  res: `PerFootprint`
632  Deblender result that contains a list of ``DeblendedPeak``\ s for each peak and (optionally)
633  the template sum.
634  """
635  avgNoise = sigma1
636 
637  debPlugins = []
638 
639  # Add activated deblender plugins
640  if fitPsfs:
641  debPlugins.append(plugins.DeblenderPlugin(plugins.fitPsfs,
642  psfChisqCut1=psfChisqCut1,
643  psfChisqCut2=psfChisqCut2,
644  psfChisqCut2b=psfChisqCut2b,
645  tinyFootprintSize=tinyFootprintSize))
646  debPlugins.append(plugins.DeblenderPlugin(plugins.buildSymmetricTemplates, patchEdges=patchEdges))
647  if rampFluxAtEdge:
648  debPlugins.append(plugins.DeblenderPlugin(plugins.rampFluxAtEdge, patchEdges=patchEdges))
649  if medianSmoothTemplate:
650  debPlugins.append(plugins.DeblenderPlugin(plugins.medianSmoothTemplates,
651  medianFilterHalfsize=medianFilterHalfsize))
652  if monotonicTemplate:
653  debPlugins.append(plugins.DeblenderPlugin(plugins.makeTemplatesMonotonic))
654  if clipFootprintToNonzero:
655  debPlugins.append(plugins.DeblenderPlugin(plugins.clipFootprintsToNonzero))
656  if weightTemplates:
657  debPlugins.append(plugins.DeblenderPlugin(plugins.weightTemplates))
658  if removeDegenerateTemplates:
659  if weightTemplates:
660  onReset = len(debPlugins)-1
661  else:
662  onReset = len(debPlugins)
663  debPlugins.append(plugins.DeblenderPlugin(plugins.reconstructTemplates,
664  onReset=onReset,
665  maxTempDotProd=maxTempDotProd))
666  debPlugins.append(plugins.DeblenderPlugin(plugins.apportionFlux,
667  clipStrayFluxFraction=clipStrayFluxFraction,
668  assignStrayFlux=assignStrayFlux,
669  strayFluxAssignment=strayFluxAssignment,
670  strayFluxToPointSources=strayFluxToPointSources,
671  getTemplateSum=getTemplateSum))
672 
673  debResult = newDeblend(debPlugins, footprint, maskedImage, psf, psffwhm, log, verbose, avgNoise)
674 
675  return debResult
676 
677 
678 def newDeblend(debPlugins, footprint, mMaskedImage, psfs, psfFwhms,
679  log=None, verbose=False, avgNoise=None, maxNumberOfPeaks=0):
680  r"""Deblend a parent ``Footprint`` in a ``MaskedImageF``.
681 
682  Deblending assumes that ``footprint`` has multiple peaks, as it will still create a
683  `PerFootprint` object with a list of peaks even if there is only one peak in the list.
684  It is recommended to first check that ``footprint`` has more than one peak, similar to the
685  execution of `lsst.meas.deblender.deblend.SourceDeblendTask`.
686 
687  This version of the deblender accepts a list of plugins to execute, with the option to re-run parts
688  of the deblender if templates are changed during any of the steps.
689 
690  Parameters
691  ----------
692  debPlugins: list of `meas.deblender.plugins.DeblenderPlugins`
693  Plugins to execute (in order of execution) for the deblender.
694  footprint: `afw.detection.Footprint` or list of Footprints
695  Parent footprint to deblend.
696  mMaskedImage: `MultibandMaskedImage` or `MaskedImage`
697  Masked image in each band.
698  psfs: `afw.detection.Psf` or list of Psfs
699  Psf of the ``maskedImage``.
700  psfFwhms: `float` or list of floats
701  FWHM of the ``maskedImage``\'s ``psf``.
702  log: `log.Log`, optional
703  LSST logger for logging purposes.
704  The default is ``None`` (no logging).
705  verbose: `bool`, optional
706  Whether or not to show a more verbose output.
707  The default is ``False``.
708  avgNoise: `float`or list of `float`\ s, optional
709  Average noise level in each ``maskedImage``.
710  The default is ``None``, which estimates the noise from the median value of the
711  variance plane of ``maskedImage`` for each filter.
712  maxNumberOfPeaks: `int`, optional
713  If nonzero, the maximum number of peaks to deblend.
714  If the total number of peaks is greater than ``maxNumberOfPeaks``,
715  then only the first ``maxNumberOfPeaks`` sources are deblended.
716 
717  Returns
718  -------
719  debResult: `DeblendedParent`
720  Deblender result that contains a list of ``MultiColorPeak``\ s for each peak and
721  information that describes the footprint in all filters.
722  """
723  # Import C++ routines
724 
725  if log is None:
726  import lsst.log as lsstLog
727 
728  log = lsstLog.Log.getLogger(__name__)
729 
730  if verbose:
731  log.setLevel(lsstLog.Log.TRACE)
732 
733  # get object that will hold our results
734  debResult = DeblenderResult(footprint, mMaskedImage, psfs, psfFwhms, log,
735  maxNumberOfPeaks=maxNumberOfPeaks, avgNoise=avgNoise)
736 
737  step = 0
738  while step < len(debPlugins):
739  # If a failure occurs at any step,
740  # the result is flagged as `failed`
741  # and the remaining steps are skipped
742  if not debResult.failed:
743  reset = debPlugins[step].run(debResult, log)
744  else:
745  log.warning("Skipping steps %s", debPlugins[step:])
746  return debResult
747  if reset:
748  step = debPlugins[step].onReset
749  else:
750  step += 1
751 
752  return debResult
753 
754 
756  """Cache the PSF models
757 
758  In the PSF fitting code, we request PSF models for all peaks near
759  the one being fit. This was turning out to be quite expensive in
760  some cases. Here, we cache the PSF models to bring the cost down
761  closer to O(N) rather than O(N^2).
762  """
763 
764  def __init__(self, psf):
765  self.cachecache = {}
766  self.psfpsf = psf
767 
768  def computeImage(self, cx, cy):
769  im = self.cachecache.get((cx, cy), None)
770  if im is not None:
771  return im
772  try:
773  im = self.psfpsf.computeImage(geom.Point2D(cx, cy))
775  im = self.psfpsf.computeImage()
776  self.cachecache[(cx, cy)] = im
777  return im
std::vector< SchemaItem< Flag > > * items
Class to describe the properties of a detected object from an image.
Definition: Footprint.h:63
def __init__(self, filterName, footprint, maskedImage, psf, psffwhm, avgNoise, maxNumberOfPeaks, debResult)
Definition: baseline.py:179
def setMedianFilteredTemplate(self, t, tfoot)
Definition: baseline.py:393
def getFluxPortion(self, strayFlux=True)
Definition: baseline.py:357
def setTemplate(self, image, footprint)
Definition: baseline.py:425
def __init__(self, peak, pki, parent, multiColorPeak=None)
Definition: baseline.py:271
def getParentProperty(self, propertyName)
Definition: baseline.py:135
def __init__(self, footprint, mMaskedImage, psfs, psffwhms, log, maxNumberOfPeaks=0, avgNoise=None)
Definition: baseline.py:75
def setTemplateSums(self, templateSums, fidx=None)
Definition: baseline.py:140
def __init__(self, peaks, pki, parent)
Definition: baseline.py:238
Provides consistent interface for LSST exceptions.
Definition: Exception.h:107
daf::base::PropertyList * list
Definition: fits.cc:913
std::shared_ptr< FrameSet > append(FrameSet const &first, FrameSet const &second)
Construct a FrameSet that performs two transformations in series.
Definition: functional.cc:33
std::shared_ptr< HeavyFootprint< ImagePixelT, MaskPixelT, VariancePixelT > > mergeHeavyFootprints(HeavyFootprint< ImagePixelT, MaskPixelT, VariancePixelT > const &h1, HeavyFootprint< ImagePixelT, MaskPixelT, VariancePixelT > const &h2)
Sum the two given HeavyFootprints h1 and h2, returning a HeavyFootprint with the union footprint,...
HeavyFootprint< ImagePixelT, MaskPixelT, VariancePixelT > makeHeavyFootprint(Footprint const &foot, lsst::afw::image::MaskedImage< ImagePixelT, MaskPixelT, VariancePixelT > const &img, HeavyFootprintCtrl const *ctrl=nullptr)
Create a HeavyFootprint with footprint defined by the given Footprint and pixel values from the given...
Backwards-compatibility support for depersisting the old Calib (FluxMag0/FluxMag0Err) objects.
Statistics makeStatistics(lsst::afw::image::Image< Pixel > const &img, lsst::afw::image::Mask< image::MaskPixel > const &msk, int const flags, StatisticsControl const &sctrl=StatisticsControl())
Handle a watered-down front-end to the constructor (no variance)
Definition: Statistics.h:359
bool any(CoordinateExpr< N > const &expr) noexcept
Return true if any elements are true.
def run(self, coaddExposures, bbox, wcs)
Definition: getTemplate.py:603
Definition: Log.h:717
def newDeblend(debPlugins, footprint, mMaskedImage, psfs, psfFwhms, log=None, verbose=False, avgNoise=None, maxNumberOfPeaks=0)
Definition: baseline.py:679
def deblend(footprint, maskedImage, psf, psffwhm, psfChisqCut1=1.5, psfChisqCut2=1.5, psfChisqCut2b=1.5, fitPsfs=True, medianSmoothTemplate=True, medianFilterHalfsize=2, monotonicTemplate=True, weightTemplates=False, log=None, verbose=False, sigma1=None, maxNumberOfPeaks=0, assignStrayFlux=True, strayFluxToPointSources='necessary', strayFluxAssignment='r-to-peak', rampFluxAtEdge=False, patchEdges=False, tinyFootprintSize=2, getTemplateSum=False, clipStrayFluxFraction=0.001, clipFootprintToNonzero=True, removeDegenerateTemplates=False, maxTempDotProd=0.5)
Definition: baseline.py:439
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
Definition: history.py:174