22__all__ = [
"DEFAULT_PLUGINS",
"DeblenderResult",
"DeblendedParent",
"MultiColorPeak",
23 "DeblendedPeak",
"deblend",
"newDeblend",
"CachingPsf"]
25from collections
import OrderedDict
48 r"""Collection of objects in multiple bands for a single parent 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.
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.
74 def __init__(self, footprint, mMaskedImage, psfs, psffwhms, log,
75 maxNumberOfPeaks=0, avgNoise=None):
78 mMaskedImage = [mMaskedImage]
81 self.
filters = mMaskedImage.filters
94 avgNoise = [
None]*len(psfs)
98 if any([len(self.
filters) != 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,
113 if maxNumberOfPeaks > 0
and maxNumberOfPeaks < self.
peakCount:
118 for n, f
in enumerate(self.
filters):
121 psffwhms[n], avgNoise[n], maxNumberOfPeaks, self)
129 self.
peaks.append(multiPeak)
136 """Get the footprint in each filter
142 self.templateSums[fidx] = templateSums
144 for f, templateSum
in templateSums.items():
149 """Deblender result of a single parent footprint, in a single band
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.
157 Name of the filter used
for `maskedImage`
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.
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``.
178 def __init__(self, filterName, footprint, maskedImage, psf, psffwhm, avgNoise,
179 maxNumberOfPeaks, debResult):
185 self.
img = maskedImage.getImage()
188 self.
mask = maskedImage.getMask()
198 avgNoise = np.sqrt(stats.getValue(afwMath.MEDIAN))
199 debResult.log.trace(
'Estimated avgNoise for filter %s = %f', self.
filter, avgNoise)
204 peaks = self.
fp.getPeaks()
207 self.
peaks.append(deblendedPeak)
210 """Update the bounding box of the parent footprint
212 If the parent Footprint is resized it will be necessary to update the bounding box of the footprint.
216 if not self.
imbb.contains(self.
bb):
217 raise ValueError((
'Footprint bounding-box %s extends outside image bounding-box %s') %
219 self.W, self.
H = self.
bb.getWidth(), self.
bb.getHeight()
220 self.x0, self.
y0 = self.
bb.getMinX(), self.
bb.getMinY()
221 self.x1, self.
y1 = self.
bb.getMaxX(), self.
bb.getMaxY()
225 """Collection of single peak deblender results in multiple bands.
227 There is one of these objects
for each Peak
in the footprint.
232 Each entry
in ``peaks``
is a peak record
for the same peak
in a different band
234 Index of the peak
in `parent.peaks`
235 parent: `DeblendedParent`
236 DeblendedParent object that contains the peak
as a child.
243 peak.multiColorPeak = self
256 """Result of deblending a single Peak within a parent Footprint.
258 There is one of these objects
for each Peak
in the Footprint.
263 Peak object
in a single band
from a peak record
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
271 def __init__(self, peak, pki, parent, multiColorPeak=None):
344 return ((
'deblend result: outOfBounds: %s, deblendedAsPsf: %s') %
359 Return a HeavyFootprint containing the flux apportioned to this peak.
361 @param[
in] strayFlux include stray flux also?
430def 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
440 r"""Deblend a parent ``Footprint`` in a ``MaskedImageF``.
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`.
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.
452 Deblending involves several mandatory
and optional steps:
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.
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``.
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``.
481 * Apply a median smoothing filter to all of the template images.
482 * Relevant parameters: ``medianFilterHalfSize``
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.
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).
497 * Re-weight the templates so that their linear combination
498 best represents the observed ``maskedImage``
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``
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``
522 Parent footprint to deblend
523 maskedImage: `afw.image.MaskedImageF`
524 Masked image containing the ``footprint``
526 Psf of the ``maskedImage``
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.
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.
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``.
580 strayFluxToPointSources: `string`
581 Determines how stray flux
is apportioned to point sources
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
587 strayFluxAssignment: `string`, optional
588 Determines how stray flux
is apportioned.
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
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.
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.
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``.
632 Deblender result that contains a list of ``DeblendedPeak``\ s
for each peak
and (optionally)
642 psfChisqCut1=psfChisqCut1,
643 psfChisqCut2=psfChisqCut2,
644 psfChisqCut2b=psfChisqCut2b,
645 tinyFootprintSize=tinyFootprintSize))
649 if medianSmoothTemplate:
651 medianFilterHalfsize=medianFilterHalfsize))
652 if monotonicTemplate:
654 if clipFootprintToNonzero:
658 if removeDegenerateTemplates:
660 onReset = len(debPlugins)-1
662 onReset = len(debPlugins)
665 maxTempDotProd=maxTempDotProd))
667 clipStrayFluxFraction=clipStrayFluxFraction,
668 assignStrayFlux=assignStrayFlux,
669 strayFluxAssignment=strayFluxAssignment,
670 strayFluxToPointSources=strayFluxToPointSources,
671 getTemplateSum=getTemplateSum))
673 debResult =
newDeblend(debPlugins, footprint, maskedImage, psf, psffwhm, log, verbose, avgNoise)
678def newDeblend(debPlugins, footprint, mMaskedImage, psfs, psfFwhms,
679 log=None, verbose=False, avgNoise=None, maxNumberOfPeaks=0):
680 r"""Deblend a parent ``Footprint`` in a ``MaskedImageF``.
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`.
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.
692 debPlugins: list of `meas.deblender.plugins.DeblenderPlugins`
693 Plugins to execute (
in order of execution)
for the deblender.
695 Parent footprint to deblend.
696 mMaskedImage: `MultibandMaskedImage`
or `MaskedImage`
697 Masked image
in each band.
699 Psf of the ``maskedImage``.
700 psfFwhms: `float`
or list of floats
701 FWHM of the ``maskedImage``\
's ``psf``.
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.
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.
728 log = lsstLog.Log.getLogger(__name__)
731 log.setLevel(lsstLog.Log.TRACE)
734 debResult =
DeblenderResult(footprint, mMaskedImage, psfs, psfFwhms, log,
735 maxNumberOfPeaks=maxNumberOfPeaks, avgNoise=avgNoise)
738 while step < len(debPlugins):
742 if not debResult.failed:
743 reset = debPlugins[step].run(debResult, log)
745 log.warning(
"Skipping steps %s", debPlugins[step:])
748 step = debPlugins[step].onReset
756 """Cache the PSF models
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).
769 im = self.
cache.get((cx, cy),
None)
776 self.
cache[(cx, cy)] = im
std::vector< SchemaItem< Flag > > * items
Record class that represents a peak in a Footprint.
A polymorphic base class for representing an image's Point Spread Function.
This static class includes a variety of methods for interacting with the the logging module.
def computeImage(self, cx, cy)
def updateFootprintBbox(self)
def __init__(self, filterName, footprint, maskedImage, psf, psffwhm, avgNoise, maxNumberOfPeaks, debResult)
def setMedianFilteredTemplate(self, t, tfoot)
def getFluxPortion(self, strayFlux=True)
def setFailedSymmetricTemplate(self)
def setPsfFitFailed(self)
def setDeblendedAsPsf(self)
def setTinyFootprint(self)
def setNoValidPixels(self)
def setRampedTemplate(self, t, tfoot)
def setOrigTemplate(self, t, tfoot)
def setPsfTemplate(self, tim, tfoot)
def setStrayFlux(self, stray)
def setFluxPortion(self, mimg)
def setTemplateWeight(self, w)
def setTemplate(self, image, footprint)
def __init__(self, peak, pki, parent, multiColorPeak=None)
def getParentProperty(self, propertyName)
def __init__(self, footprint, mMaskedImage, psfs, psffwhms, log, maxNumberOfPeaks=0, avgNoise=None)
def setTemplateSums(self, templateSums, fidx=None)
def __init__(self, peaks, pki, parent)
Provides consistent interface for LSST exceptions.
daf::base::PropertyList * list
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...
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)
def newDeblend(debPlugins, footprint, mMaskedImage, psfs, psfFwhms, log=None, verbose=False, avgNoise=None, maxNumberOfPeaks=0)
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)