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