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