1 from builtins
import zip
2 from builtins
import str
3 from builtins
import range
4 from builtins
import object
28 from collections
import OrderedDict
50 """Collection of objects in multiple bands for a single parent footprint
53 def __init__(self, footprints, maskedImages, psfs, psffwhms, log, filters=None,
54 maxNumberOfPeaks=0, avgNoise=
None):
55 """ Initialize a DeblededParent
59 footprint: list of `afw.detection.Footprint`s
60 Parent footprint to deblend in each band.
61 maskedImages: list of `afw.image.MaskedImageF`s
62 Masked image containing the ``footprint`` in each band.
63 psf: list of `afw.detection.Psf`s
64 Psf of the ``maskedImage`` for each band.
65 psffwhm: list of `float`s
66 FWHM of the ``maskedImage``'s ``psf`` in each band.
67 filters: list of `string`, optional
68 Names of the filters. This should be the same length as the other lists
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.
86 footprints = [footprints]
90 maskedImages = [maskedImages]
102 avgNoise = [avgNoise]
104 if any([len(footprints)!=len(p)
for p
in [maskedImages, psfs, psffwhms, avgNoise]]):
105 raise ValueError(
"To use the multi-color deblender, "
106 "'footprint', 'maskedImage', 'psf', 'psffwhm', 'avgNoise'"
107 "must have the same length, but instead have lengths: "
108 "{0}".
format([len(p)
for p
in [footprints,
118 if maxNumberOfPeaks>0
and maxNumberOfPeaks<self.
peakCount:
122 filters = range(len(footprints))
130 psffwhms[n], avgNoise[n], maxNumberOfPeaks, self)
135 peakDict = {f: dp.peaks[idx]
for f,dp
in self.deblendedParents.items()}
139 """Get the footprint in each filter"""
144 self.templateSums[fidx] = templateSums
146 for f, templateSum
in templateSums.items():
150 """Deblender result of a single parent footprint, in a single band
152 Convenience class to store useful objects used by the deblender for a single band,
153 such as the maskedImage, psf, etc as well as the results from the deblender.
155 def __init__(self, filterName, footprint, maskedImage, psf, psffwhm, avgNoise,
156 maxNumberOfPeaks, debResult):
157 """Create a DeblendedParent to store a deblender result
162 Name of the filter used for `maskedImage`
163 footprint: list of `afw.detection.Footprint`s
164 Parent footprint to deblend in each band.
165 maskedImages: list of `afw.image.MaskedImageF`s
166 Masked image containing the ``footprint`` in each band.
167 psf: list of `afw.detection.Psf`s
168 Psf of the ``maskedImage`` for each band.
169 psffwhm: list of `float`s
170 FWHM of the ``maskedImage``'s ``psf`` in each band.
171 avgNoise: `float`or list of `float`s, optional
172 Average noise level in each ``maskedImage``.
173 The default is ``None``, which estimates the noise from the median value of the
174 variance plane of ``maskedImage`` for each filter.
175 maxNumberOfPeaks: `int`, optional
176 If positive, the maximum number of peaks to deblend.
177 If the total number of peaks is greater than ``maxNumberOfPeaks``,
178 then only the first ``maxNumberOfPeaks`` sources are deblended.
179 The default is 0, which deblends all of the peaks.
180 debResult: `DeblenderResult`
181 The ``DeblenderResult`` that contains this ``DeblendedParent``.
189 self.
img = maskedImage.getImage()
192 self.
mask = maskedImage.getMask()
202 avgNoise = math.sqrt(stats.getValue(afwMath.MEDIAN))
203 debResult.log.trace(
'Estimated avgNoise for filter %s = %f', self.
filter, avgNoise)
208 peaks = self.fp.getPeaks()
211 self.peaks.append(deblendedPeak)
214 """Update the bounding box of the parent footprint
216 If the parent Footprint is resized it will be necessary to update the bounding box of the footprint.
219 self.
bb = self.fp.getBBox()
220 if not self.imbb.contains(self.
bb):
221 raise ValueError((
'Footprint bounding-box %s extends outside image bounding-box %s') %
222 (str(self.
bb), str(self.
imbb)))
223 self.W, self.
H = self.bb.getWidth(), self.bb.getHeight()
224 self.x0, self.
y0 = self.bb.getMinX(), self.bb.getMinY()
225 self.x1, self.
y1 = self.bb.getMaxX(), self.bb.getMaxY()
228 """Collection of single peak deblender results in multiple bands.
230 There is one of these objects for each Peak in the footprint.
234 """Create a collection for deblender results in each band.
238 peaks: `dict` of `afw.detection.PeakRecord`
239 Each entry in ``peaks`` is a peak record for the same peak in a different band
241 Index of the peak in `parent.peaks`
242 parent: `DeblendedParent`
243 DeblendedParent object that contains the peak as a child.
253 for pki, peak
in self.deblendedPeaks.items():
254 peak.multiColorPeak = self
264 """Result of deblending a single Peak within a parent Footprint.
266 There is one of these objects for each Peak in the Footprint.
269 def __init__(self, peak, pki, parent, multiColorPeak=None):
270 """Initialize a new deblended peak in a single filter band
274 peak: `afw.detection.PeakRecord`
275 Peak object in a single band from a peak record
277 Index of the peak in `multiColorPeak.parent.peaks`
278 parent: `DeblendedParent`
279 Parent in the same filter that contains the peak
280 multiColorPeak: `MultiColorPeak`
281 Object containing the same peak in multiple bands
359 return ((
'deblend result: outOfBounds: %s, deblendedAsPsf: %s') %
374 Return a HeavyFootprint containing the flux apportioned to this peak.
376 @param[in] strayFlux include stray flux also?
384 self.strayFlux.normalize()
385 heavy = afwDet.mergeHeavyFootprintsF(heavy, self.
strayFlux)
446 def deblend(footprint, maskedImage, psf, psffwhm, filters=None,
447 psfChisqCut1=1.5, psfChisqCut2=1.5, psfChisqCut2b=1.5, fitPsfs=
True,
448 medianSmoothTemplate=
True, medianFilterHalfsize=2,
449 monotonicTemplate=
True, weightTemplates=
False,
450 log=
None, verbose=
False, sigma1=
None, maxNumberOfPeaks=0,
451 assignStrayFlux=
True, strayFluxToPointSources=
'necessary', strayFluxAssignment=
'r-to-peak',
452 rampFluxAtEdge=
False, patchEdges=
False, tinyFootprintSize=2,
453 getTemplateSum=
False, clipStrayFluxFraction=0.001, clipFootprintToNonzero=
True,
454 removeDegenerateTemplates=
False, maxTempDotProd=0.5
456 """Deblend a parent ``Footprint`` in a ``MaskedImageF``.
458 Deblending assumes that ``footprint`` has multiple peaks, as it will still create a
459 `PerFootprint` object with a list of peaks even if there is only one peak in the list.
460 It is recommended to first check that ``footprint`` has more than one peak, similar to the
461 execution of `lsst.meas.deblender.deblend.SourceDeblendTask`.
464 This is the API for the old deblender, however the function builds the plugins necessary
465 to use the new deblender to perform identically to the old deblender.
466 To test out newer functionality use ``newDeblend`` instead.
468 Deblending involves several mandatory and optional steps:
469 # Optional: If ``fitPsfs`` is True, find all peaks that are well-fit by a PSF + background model
470 * Peaks that pass the cuts have their footprints modified to the PSF + background model
471 and their ``deblendedAsPsf`` property set to ``True``.
472 * Relevant parameters: ``psfChisqCut1``, ``psfChisqCut2``, ``psfChisqCut2b``,
473 ``tinyFootprintSize``.
474 * See the parameter descriptions for more.
475 # Build a symmetric template for each peak not well-fit by the PSF model
476 * Given ``maskedImageF``, ``footprint``, and a ``DeblendedPeak``, creates a symmetric
477 template (``templateImage`` and ``templateFootprint``) around the peak
478 for all peaks not flagged as ``skip`` or ``deblendedAsPsf``.
479 * If ``patchEdges=True`` and if ``footprint`` touches pixels with the
480 ``EDGE`` bit set, then ``footprint`` is grown to include spans whose
481 symmetric mirror is outside of the image.
482 * Relevant parameters: ``sigma1`` and ``patchEdges``.
483 # Optional: If ``rampFluxAtEdge`` is True, adjust flux on the edges of the template footprints
484 * Using the PSF, a peak ``Footprint`` with pixels on the edge of of ``footprint``
485 is grown by the psffwhm*1.5 and filled in with zeros.
486 * The result is a new symmetric footprint template for the peaks near the edge.
487 * Relevant parameter: ``patchEdges``.
488 # Optionally (``medianSmoothTemplate=True``) filter the template images
489 * Apply a median smoothing filter to all of the template images.
490 * Relevant parameters: ``medianFilterHalfSize``
491 # Optional: If ``monotonicTemplate`` is True, make the templates monotonic.
492 * The pixels in the templates are modified such that pixels
493 further from the peak will have values smaller than those closer to the peak.
494 # Optional: If ``clipFootprintToNonzero`` is True, clip non-zero spans in the template footprints
495 * Peak ``Footprint``s are clipped to the region in the image containing non-zero values
496 by dropping spans that are completely zero and moving endpoints to non-zero pixels
497 (but does not split spans that have internal zeros).
498 # Optional: If ``weightTemplates`` is True, weight the templates to best fit the observed image
499 * Re-weight the templates so that their linear combination
500 best represents the observed ``maskedImage``
501 # Optional: If ``removeDegenerateTempaltes`` is True, reconstruct shredded galaxies
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 # Apportion flux to all of the peak templates
511 * Divide the ``maskedImage`` flux amongst all of the templates based on the fraction of
512 flux assigned to each ``tempalteFootprint``.
513 * Leftover "stray flux" is assigned to peaks based on the other parameters.
514 * Relevant parameters: ``clipStrayFluxFraction``, ``strayFluxAssignment``,
515 ``strayFluxToPointSources``, ``assignStrayFlux``
519 footprint: `afw.detection.Footprint`
520 Parent footprint to deblend
521 maskedImage: `afw.image.MaskedImageF`
522 Masked image containing the ``footprint``
523 psf: `afw.detection.Psf`
524 Psf of the ``maskedImage``
526 FWHM of the ``maskedImage``'s ``psf``
527 psfChisqCut*: `float`, optional
528 If ``fitPsfs==True``, all of the peaks are fit to the image PSF.
529 ``psfChisqCut1`` is the maximum chi-squared-per-degree-of-freedom allowed for a peak to
530 be considered a PSF match without recentering.
531 A fit is also made that includes terms to recenter the PSF.
532 ``psfChisqCut2`` is the same as ``psfChisqCut1`` except it determines the restriction on the
533 fit that includes recentering terms.
534 If the peak is a match for a re-centered PSF, the PSF is repositioned at the new center and
535 the peak footprint is fit again, this time to the new PSF.
536 If the resulting chi-squared-per-degree-of-freedom is less than ``psfChisqCut2b`` then it
537 passes the re-centering algorithm.
538 If the peak passes both the re-centered and fixed position cuts, the better of the two is accepted,
539 but parameters for all three psf fits are stored in the ``DeblendedPeak``.
540 The default for ``psfChisqCut1``, ``psfChisqCut2``, and ``psfChisqCut2b`` is ``1.5``.
541 fitPsfs: `bool`, optional
542 If True then all of the peaks will be compared to the image PSF to
543 distinguish stars from galaxies.
544 medianSmoothTemplate: ``bool``, optional
545 If ``medianSmoothTemplate==True`` it a median smoothing filter is applied to the ``maskedImage``.
546 The default is ``True``.
547 medianFilterHalfSize: `int`, optional
548 Half the box size of the median filter, ie a ``medianFilterHalfSize`` of 50 means that
549 each output pixel will be the median of the pixels in a 101 x 101-pixel box in the input image.
550 This parameter is only used when ``medianSmoothTemplate==True``, otherwise it is ignored.
551 The default value is 2.
552 monotonicTempalte: `bool`, optional
553 If True then make the template monotonic.
555 weightTemplates: `bool`, optional
556 If True, re-weight the templates so that their linear combination best represents
557 the observed ``maskedImage``.
558 The default is False.
559 log: `log.Log`, optional
560 LSST logger for logging purposes.
561 The default is ``None`` (no logging).
562 verbose: `bool`, optional
563 Whether or not to show a more verbose output.
564 The default is ``False``.
565 sigma1: `float`, optional
566 Average noise level in ``maskedImage``.
567 The default is ``None``, which estimates the noise from the median value of ``maskedImage``.
568 maxNumberOfPeaks: `int`, optional
569 If nonzero, the maximum number of peaks to deblend.
570 If the total number of peaks is greater than ``maxNumberOfPeaks``,
571 then only the first ``maxNumberOfPeaks`` sources are deblended.
572 The default is 0, which deblends all of the peaks.
573 assignStrayFlux: `bool`, optional
574 If True then flux in the parent footprint that is not covered by any of the
575 template footprints is assigned to templates based on their 1/(1+r^2) distance.
576 How the flux is apportioned is determined by ``strayFluxAssignment``.
578 strayFluxToPointSources: `string`
579 Determines how stray flux is apportioned to point sources
580 * ``never``: never apportion stray flux to point sources
581 * ``necessary`` (default): point sources are included only if there are no extended sources nearby
582 * ``always``: point sources are always included in the 1/(1+r^2) splitting
583 strayFluxAssignment: `string`, optional
584 Determines how stray flux is apportioned.
585 * ``trim``: Trim stray flux and do not include in any footprints
586 * ``r-to-peak`` (default): Stray flux is assigned based on (1/(1+r^2) from the peaks
587 * ``r-to-footprint``: Stray flux is distributed to the footprints based on 1/(1+r^2) of the
588 minimum distance from the stray flux to footprint
589 * ``nearest-footprint``: Stray flux is assigned to the footprint with lowest L-1 (Manhattan)
590 distance to the stray flux
591 rampFluxAtEdge: `bool`, optional
592 If True then extend footprints with excessive flux on the edges as described above.
593 The default is False.
594 patchEdges: `bool`, optional
595 If True and if the footprint touches pixels with the ``EDGE`` bit set,
596 then grow the footprint to include all symmetric templates.
597 The default is ``False``.
598 tinyFootprintSize: `float`, optional
599 The PSF model is shrunk to the size that contains the original footprint.
600 If the bbox of the clipped PSF model for a peak is smaller than ``max(tinyFootprintSize,2)``
601 then ``tinyFootprint`` for the peak is set to ``True`` and the peak is not fit.
603 getTemplateSum: `bool`, optional
604 As part of the flux calculation, the sum of the templates is calculated.
605 If ``getTemplateSum==True`` then the sum of the templates is stored in the result (a `PerFootprint`).
606 The default is False.
607 clipStrayFluxFraction: `float`, optional
608 Minimum stray-flux portion.
609 Any stray-flux portion less than ``clipStrayFluxFraction`` is clipped to zero.
610 The default is 0.001.
611 clipFootprintToNonzero: `bool`, optional
612 If True then clip non-zero spans in the template footprints. See above for more.
614 removeDegenerateTemplates: `bool`, optional
615 If True then we try to identify "degenerate" peaks by looking at the inner product
616 (in pixel space) of pairs of templates.
617 The default is False.
618 maxTempDotProduct: `float`, optional
619 All dot products between templates greater than ``maxTempDotProduct`` will result in one
620 of the templates removed. This parameter is only used when ``removeDegenerateTempaltes==True``.
626 Deblender result that contains a list of ``DeblendedPeak``s for each peak and (optionally)
636 psfChisqCut1=psfChisqCut1,
637 psfChisqCut2=psfChisqCut2,
638 psfChisqCut2b=psfChisqCut2b,
639 tinyFootprintSize=tinyFootprintSize))
643 if medianSmoothTemplate:
645 medianFilterHalfsize=medianFilterHalfsize))
646 if monotonicTemplate:
648 if clipFootprintToNonzero:
652 if removeDegenerateTemplates:
654 onReset = len(debPlugins)-1
656 onReset = len(debPlugins)
659 maxTempDotProd=maxTempDotProd))
661 clipStrayFluxFraction=clipStrayFluxFraction,
662 assignStrayFlux=assignStrayFlux,
663 strayFluxAssignment=strayFluxAssignment,
664 strayFluxToPointSources=strayFluxToPointSources,
665 getTemplateSum=getTemplateSum))
667 debResult =
newDeblend(debPlugins, footprint, maskedImage, psf, psffwhm, filters, log, verbose, avgNoise)
671 def newDeblend(debPlugins, footprint, maskedImage, psf, psffwhm, filters=None,
672 log=
None, verbose=
False, avgNoise=
None, maxNumberOfPeaks=0):
673 """Deblend a parent ``Footprint`` in a ``MaskedImageF``.
675 Deblending assumes that ``footprint`` has multiple peaks, as it will still create a
676 `PerFootprint` object with a list of peaks even if there is only one peak in the list.
677 It is recommended to first check that ``footprint`` has more than one peak, similar to the
678 execution of `lsst.meas.deblender.deblend.SourceDeblendTask`.
680 This version of the deblender accepts a list of plugins to execute, with the option to re-run parts
681 of the deblender if templates are changed during any of the steps.
685 debPlugins: list of `meas.deblender.plugins.DeblenderPlugins`
686 Plugins to execute (in order of execution) for the deblender.
687 footprint: `afw.detection.Footprint` or list of Footprints
688 Parent footprint to deblend.
689 maskedImage: `afw.image.MaskedImageF` or list of MaskedImages
690 Masked image containing the ``footprint``.
691 psf: `afw.detection.Psf` or list of Psfs
692 Psf of the ``maskedImage``.
693 psffwhm: `float` or list of floats
694 FWHM of the ``maskedImage``'s ``psf``.
695 filters: list of `string`s, optional
696 Names of the filters when ``footprint`` is a list instead of a single ``footprint``.
697 This is used to index the deblender results by filter.
698 The default is None, which will numerically index lists of objects.
699 log: `log.Log`, optional
700 LSST logger for logging purposes.
701 The default is ``None`` (no logging).
702 verbose: `bool`, optional
703 Whether or not to show a more verbose output.
704 The default is ``False``.
705 avgNoise: `float`or list of `float`s, optional
706 Average noise level in each ``maskedImage``.
707 The default is ``None``, which estimates the noise from the median value of the
708 variance plane of ``maskedImage`` for each filter.
709 maxNumberOfPeaks: `int`, optional
710 If nonzero, the maximum number of peaks to deblend.
711 If the total number of peaks is greater than ``maxNumberOfPeaks``,
712 then only the first ``maxNumberOfPeaks`` sources are deblended.
716 debResult: `DeblendedParent`
717 Deblender result that contains a list of ``MultiColorPeak``s for each peak and
718 information that describes the footprint in all filters.
722 butils = deb.BaselineUtilsF
727 component =
'meas_deblender.baseline'
728 log = lsstLog.Log.getLogger(component)
731 log.setLevel(lsstLog.Log.TRACE)
734 debResult =
DeblenderResult(footprint, maskedImage, psf, psffwhm, log, filters=filters,
735 maxNumberOfPeaks=maxNumberOfPeaks, avgNoise=avgNoise)
738 while step < len(debPlugins):
739 reset = debPlugins[step].
run(debResult, log)
741 step = debPlugins[step].onReset
749 """Cache the PSF models
751 In the PSF fitting code, we request PSF models for all peaks near
752 the one being fit. This was turning out to be quite expensive in
753 some cases. Here, we cache the PSF models to bring the cost down
754 closer to O(N) rather than O(N^2).
762 im = self.cache.get((cx, cy),
None)
768 im = self.psf.computeImage()
769 self.
cache[(cx, cy)] = im
bool any(CoordinateExpr< N > const &expr)
Return true if any elements are true.
def getFluxPortion
Return a HeavyFootprint containing the flux apportioned to this peak.
def run
Exit with the status code resulting from running the provided test suite.
Statistics makeStatistics(afwImage::Mask< afwImage::MaskPixel > const &msk, int const flags, StatisticsControl const &sctrl)
Specialization to handle Masks.
def setMedianFilteredTemplate
def setFailedSymmetricTemplate
HeavyFootprint< ImagePixelT, MaskPixelT, VariancePixelT > makeHeavyFootprint(Footprint const &foot, lsst::afw::image::MaskedImage< ImagePixelT, MaskPixelT, VariancePixelT > const &img, HeavyFootprintCtrl const *ctrl=NULL)
Create a HeavyFootprint with footprint defined by the given Footprint and pixel values from the given...