24__all__ = (
"SourceDetectionConfig",
"SourceDetectionTask",
"addExposures")
26from contextlib
import contextmanager
34import lsst.afw.image
as afwImage
39from lsst.utils.timer
import timeMethod
40from .subtractBackground
import SubtractBackgroundTask, backgroundFlatContext
44 """Configuration parameters for the SourceDetectionTask
46 minPixels = pexConfig.RangeField(
47 doc=
"detected sources with fewer than the specified number of pixels will be ignored",
48 dtype=int, optional=
False, default=1, min=0,
50 isotropicGrow = pexConfig.Field(
51 doc=
"Grow pixels as isotropically as possible? If False, use a Manhattan metric instead.",
52 dtype=bool, default=
True,
54 combinedGrow = pexConfig.Field(
55 doc=
"Grow all footprints at the same time? This allows disconnected footprints to merge.",
56 dtype=bool, default=
True,
58 nSigmaToGrow = pexConfig.Field(
59 doc=
"Grow detections by nSigmaToGrow * [PSF RMS width]; if 0 then do not grow",
60 dtype=float, default=2.4,
62 returnOriginalFootprints = pexConfig.Field(
63 doc=
"Grow detections to set the image mask bits, but return the original (not-grown) footprints",
64 dtype=bool, optional=
False, default=
False,
66 thresholdValue = pexConfig.RangeField(
67 doc=
"Threshold for detecting footprints; exact meaning and units depend on thresholdType.",
68 dtype=float, optional=
False, default=5.0, min=0.0,
70 includeThresholdMultiplier = pexConfig.RangeField(
71 doc=
"Multiplier on thresholdValue for whether a source is included in the output catalog."
72 " For example, thresholdValue=5, includeThresholdMultiplier=10, thresholdType='pixel_stdev' "
73 "results in a catalog of sources at >50 sigma with the detection mask and footprints "
74 "including pixels >5 sigma.",
75 dtype=float, default=1.0, min=0.0,
77 thresholdType = pexConfig.ChoiceField(
78 doc=
"Specifies the meaning of thresholdValue.",
79 dtype=str, optional=
False, default=
"pixel_stdev",
81 "variance":
"threshold applied to image variance",
82 "stdev":
"threshold applied to image std deviation",
83 "value":
"threshold applied to image value",
84 "pixel_stdev":
"threshold applied to per-pixel std deviation",
87 thresholdPolarity = pexConfig.ChoiceField(
88 doc=
"Specifies whether to detect positive, or negative sources, or both.",
89 dtype=str, optional=
False, default=
"positive",
91 "positive":
"detect only positive sources",
92 "negative":
"detect only negative sources",
93 "both":
"detect both positive and negative sources",
96 adjustBackground = pexConfig.Field(
98 doc=
"Fiddle factor to add to the background; debugging only",
101 reEstimateBackground = pexConfig.Field(
103 doc=
"Estimate the background again after final source detection?",
104 default=
True, optional=
False,
106 doApplyFlatBackgroundRatio = pexConfig.Field(
107 doc=
"Convert from a photometrically flat image to one suitable for background subtraction? "
108 "Only used if reEstimateBackground is True."
109 "If True, then a backgroundToPhotometricRatio must be supplied to the task run method.",
113 background = pexConfig.ConfigurableField(
114 doc=
"Background re-estimation; ignored if reEstimateBackground false",
115 target=SubtractBackgroundTask,
117 tempLocalBackground = pexConfig.ConfigurableField(
118 doc=(
"A local (small-scale), temporary background estimation step run between "
119 "detecting above-threshold regions and detecting the peaks within "
120 "them; used to avoid detecting spuerious peaks in the wings."),
121 target=SubtractBackgroundTask,
123 doTempLocalBackground = pexConfig.Field(
125 doc=
"Enable temporary local background subtraction? (see tempLocalBackground)",
128 tempWideBackground = pexConfig.ConfigurableField(
129 doc=(
"A wide (large-scale) background estimation and removal before footprint and peak detection. "
130 "It is added back into the image after detection. The purpose is to suppress very large "
131 "footprints (e.g., from large artifacts) that the deblender may choke on."),
132 target=SubtractBackgroundTask,
134 doTempWideBackground = pexConfig.Field(
136 doc=
"Do temporary wide (large-scale) background subtraction before footprint detection?",
139 nPeaksMaxSimple = pexConfig.Field(
141 doc=(
"The maximum number of peaks in a Footprint before trying to "
142 "replace its peaks using the temporary local background"),
145 nSigmaForKernel = pexConfig.Field(
147 doc=(
"Multiple of PSF RMS size to use for convolution kernel bounding box size; "
148 "note that this is not a half-size. The size will be rounded up to the nearest odd integer"),
151 statsMask = pexConfig.ListField(
153 doc=
"Mask planes to ignore when calculating statistics of image (for thresholdType=stdev)",
154 default=[
'BAD',
'SAT',
'EDGE',
'NO_DATA'],
159 doc=
"Mask planes to exclude when detecting sources."
172 for maskPlane
in (
"DETECTED",
"DETECTED_NEGATIVE"):
178 """Detect peaks and footprints of sources in an image.
180 This task expects the image to have been background subtracted first.
181 Running detection on images with a non-zero-centered background may result
182 in a single source detected on the entire image containing thousands of
183 peaks, or other pathological outputs.
187 schema : `lsst.afw.table.Schema`
188 Schema object used to create the output `lsst.afw.table.SourceCatalog`
190 Keyword arguments passed to `lsst.pipe.base.Task.__init__`
192 If schema is not None and configured for 'both' detections,
193 a 'flags.negative' field will be added to label detections made with a
198 This task convolves the image with a Gaussian approximation to the PSF,
199 matched to the sigma of the input exposure, because this is separable and
200 fast. The PSF would have to be very non-Gaussian or non-circular for this
201 approximation to have a significant impact on the signal-to-noise of the
204 ConfigClass = SourceDetectionConfig
205 _DefaultName =
"sourceDetection"
208 pipeBase.Task.__init__(self, **kwds)
209 if schema
is not None and self.config.thresholdPolarity ==
"both":
211 "is_negative", type=
"Flag",
212 doc=
"Set if source peak was detected as negative."
215 if self.config.thresholdPolarity ==
"both":
216 self.log.warning(
"Detection polarity set to 'both', but no flag will be "
217 "set to distinguish between positive and negative detections")
219 if self.config.reEstimateBackground:
220 self.makeSubtask(
"background")
221 if self.config.doTempLocalBackground:
222 self.makeSubtask(
"tempLocalBackground")
223 if self.config.doTempWideBackground:
224 self.makeSubtask(
"tempWideBackground")
227 def run(self, table, exposure, doSmooth=True, sigma=None, clearMask=True, expId=None,
228 background=None, backgroundToPhotometricRatio=None):
229 r"""Detect sources and return catalog(s) of detections.
233 table : `lsst.afw.table.SourceTable`
234 Table object that will be used to create the SourceCatalog.
235 exposure : `lsst.afw.image.Exposure`
236 Exposure to process; DETECTED mask plane will be set in-place.
237 doSmooth : `bool`, optional
238 If True, smooth the image before detection using a Gaussian of width
239 ``sigma``, or the measured PSF width. Set to False when running on
240 e.g. a pre-convolved image, or a mask plane.
241 sigma : `float`, optional
242 Sigma of PSF (pixels); used for smoothing and to grow detections;
243 if None then measure the sigma of the PSF of the exposure
244 clearMask : `bool`, optional
245 Clear DETECTED{,_NEGATIVE} planes before running detection.
246 expId : `int`, optional
247 Exposure identifier; unused by this implementation, but used for
248 RNG seed by subclasses.
249 background : `lsst.afw.math.BackgroundList`, optional
250 Background that was already subtracted from the exposure; will be
251 modified in-place if ``reEstimateBackground=True``.
252 backgroundToPhotometricRatio : `lsst.afw.image.Image`, optional
253 Image to convert photometric-flattened image to
254 background-flattened image if ``reEstimateBackground=True`` and
255 exposure has been photometric-flattened.
259 result : `lsst.pipe.base.Struct`
260 The `~lsst.pipe.base.Struct` contains:
263 Detected sources on the exposure.
264 (`lsst.afw.table.SourceCatalog`)
266 Positive polarity footprints.
267 (`lsst.afw.detection.FootprintSet` or `None`)
269 Negative polarity footprints.
270 (`lsst.afw.detection.FootprintSet` or `None`)
272 Number of footprints in positive or 0 if detection polarity was
275 Number of footprints in negative or 0 if detection polarity was
278 Re-estimated background. `None` if
279 ``reEstimateBackground==False``.
280 (`lsst.afw.math.BackgroundList`)
282 Multiplication factor applied to the configured detection
288 Raised if flags.negative is needed, but isn't in table's schema.
289 lsst.pipe.base.TaskError
290 Raised if sigma=None, doSmooth=True and the exposure has no PSF.
294 If you want to avoid dealing with Sources and Tables, you can use
295 `detectFootprints()` to just get the
296 `~lsst.afw.detection.FootprintSet`\s.
299 raise ValueError(
"Table has incorrect Schema")
300 results = self.
detectFootprints(exposure=exposure, doSmooth=doSmooth, sigma=sigma,
301 clearMask=clearMask, expId=expId, background=background,
302 backgroundToPhotometricRatio=backgroundToPhotometricRatio)
304 sources.reserve(results.numPos + results.numNeg)
306 results.negative.makeSources(sources)
308 for record
in sources:
311 results.positive.makeSources(sources)
312 results.sources = sources
315 def display(self, exposure, results, convolvedImage=None):
316 """Display detections if so configured
318 Displays the ``exposure`` in frame 0, overlays the detection peaks.
320 Requires that ``lsstDebug`` has been set up correctly, so that
321 ``lsstDebug.Info("lsst.meas.algorithms.detection")`` evaluates `True`.
323 If the ``convolvedImage`` is non-`None` and
324 ``lsstDebug.Info("lsst.meas.algorithms.detection") > 1``, the
325 ``convolvedImage`` will be displayed in frame 1.
329 exposure : `lsst.afw.image.Exposure`
330 Exposure to display, on which will be plotted the detections.
331 results : `lsst.pipe.base.Struct`
332 Results of the 'detectFootprints' method, containing positive and
333 negative footprints (which contain the peak positions that we will
334 plot). This is a `Struct` with ``positive`` and ``negative``
335 elements that are of type `lsst.afw.detection.FootprintSet`.
336 convolvedImage : `lsst.afw.image.Image`, optional
337 Convolved image used for thresholding.
350 afwDisplay.setDefaultMaskTransparency(75)
352 disp0 = afwDisplay.Display(frame=0)
353 disp0.mtv(exposure, title=
"detection")
355 def plotPeaks(fps, ctype):
358 with disp0.Buffering():
359 for fp
in fps.getFootprints():
360 for pp
in fp.getPeaks():
361 disp0.dot(
"+", pp.getFx(), pp.getFy(), ctype=ctype)
362 plotPeaks(results.positive,
"yellow")
363 plotPeaks(results.negative,
"red")
365 if convolvedImage
and display > 1:
366 disp1 = afwDisplay.Display(frame=1)
367 disp1.mtv(convolvedImage, title=
"PSF smoothed")
369 disp2 = afwDisplay.Display(frame=2)
370 disp2.mtv(afwImage.ImageF(np.sqrt(exposure.variance.array)), title=
"stddev")
373 """Apply a temporary local background subtraction
375 This temporary local background serves to suppress noise fluctuations
376 in the wings of bright objects.
378 Peaks in the footprints will be updated.
382 exposure : `lsst.afw.image.Exposure`
383 Exposure for which to fit local background.
384 middle : `lsst.afw.image.MaskedImage`
385 Convolved image on which detection will be performed
386 (typically smaller than ``exposure`` because the
387 half-kernel has been removed around the edges).
388 results : `lsst.pipe.base.Struct`
389 Results of the 'detectFootprints' method, containing positive and
390 negative footprints (which contain the peak positions that we will
391 plot). This is a `Struct` with ``positive`` and ``negative``
392 elements that are of type `lsst.afw.detection.FootprintSet`.
397 bg = self.tempLocalBackground.fitBackground(exposure.getMaskedImage())
398 bgImage = bg.getImageF(self.tempLocalBackground.config.algorithm,
399 self.tempLocalBackground.config.undersampleStyle)
400 middle -= bgImage.Factory(bgImage, middle.getBBox())
401 if self.config.thresholdPolarity !=
"negative":
402 results.positiveThreshold = self.
makeThreshold(middle,
"positive")
403 self.
updatePeaks(results.positive, middle, results.positiveThreshold)
404 if self.config.thresholdPolarity !=
"positive":
405 results.negativeThreshold = self.
makeThreshold(middle,
"negative")
406 self.
updatePeaks(results.negative, middle, results.negativeThreshold)
409 """Clear the DETECTED and DETECTED_NEGATIVE mask planes.
411 Removes any previous detection mask in preparation for a new
416 mask : `lsst.afw.image.Mask`
419 mask &= ~(mask.getPlaneBitMask(
"DETECTED") | mask.getPlaneBitMask(
"DETECTED_NEGATIVE"))
422 """Calculate the size of the smoothing kernel.
424 Uses the ``nSigmaForKernel`` configuration parameter. Note
425 that that is the full width of the kernel bounding box
426 (so a value of 7 means 3.5 sigma on either side of center).
427 The value will be rounded up to the nearest odd integer.
432 Gaussian sigma of smoothing kernel.
437 Size of the smoothing kernel.
439 return (int(sigma * self.config.nSigmaForKernel + 0.5)//2)*2 + 1
442 """Create a single Gaussian PSF for an exposure.
444 If ``sigma`` is provided, we make a `~lsst.afw.detection.GaussianPsf`
445 with that, otherwise use the sigma from the psf of the ``exposure`` to
446 make the `~lsst.afw.detection.GaussianPsf`.
450 exposure : `lsst.afw.image.Exposure`
451 Exposure from which to retrieve the PSF.
452 sigma : `float`, optional
453 Gaussian sigma to use if provided.
457 psf : `lsst.afw.detection.GaussianPsf`
458 PSF to use for detection.
463 Raised if ``sigma`` is not provided and ``exposure`` does not
464 contain a ``Psf`` object.
467 psf = exposure.getPsf()
469 raise RuntimeError(
"Unable to determine PSF to use for detection: no sigma provided")
470 sigma = psf.computeShape(psf.getAveragePosition()).getDeterminantRadius()
476 """Convolve the image with the PSF.
478 We convolve the image with a Gaussian approximation to the PSF,
479 because this is separable and therefore fast. It's technically a
480 correlation rather than a convolution, but since we use a symmetric
481 Gaussian there's no difference.
483 The convolution can be disabled with ``doSmooth=False``. If we do
484 convolve, we mask the edges as ``EDGE`` and return the convolved image
485 with the edges removed. This is because we can't convolve the edges
486 because the kernel would extend off the image.
490 maskedImage : `lsst.afw.image.MaskedImage`
492 psf : `lsst.afw.detection.Psf`
493 PSF to convolve with (actually with a Gaussian approximation
496 Actually do the convolution? Set to False when running on
497 e.g. a pre-convolved image, or a mask plane.
501 results : `lsst.pipe.base.Struct`
502 The `~lsst.pipe.base.Struct` contains:
505 Convolved image, without the edges. (`lsst.afw.image.MaskedImage`)
507 Gaussian sigma used for the convolution. (`float`)
509 self.metadata[
"doSmooth"] = doSmooth
510 sigma = psf.computeShape(psf.getAveragePosition()).getDeterminantRadius()
511 self.metadata[
"sigma"] = sigma
514 middle = maskedImage.Factory(maskedImage, deep=
True)
515 return pipeBase.Struct(middle=middle, sigma=sigma)
520 self.metadata[
"smoothingKernelWidth"] = kWidth
521 gaussFunc = afwMath.GaussianFunction1D(sigma)
524 convolvedImage = maskedImage.Factory(maskedImage.getBBox())
529 goodBBox = gaussKernel.shrinkBBox(convolvedImage.getBBox())
530 middle = convolvedImage.Factory(convolvedImage, goodBBox, afwImage.PARENT,
False)
533 self.
setEdgeBits(maskedImage, goodBBox, maskedImage.getMask().getPlaneBitMask(
"EDGE"))
535 return pipeBase.Struct(middle=middle, sigma=sigma)
538 r"""Apply thresholds to the convolved image
540 Identifies `~lsst.afw.detection.Footprint`\s, both positive and negative.
541 The threshold can be modified by the provided multiplication
546 middle : `lsst.afw.image.MaskedImage`
547 Convolved image to threshold.
548 bbox : `lsst.geom.Box2I`
549 Bounding box of unconvolved image.
551 Multiplier for the configured threshold.
552 factorNeg : `float` or `None`
553 Multiplier for the configured threshold for negative detection polarity.
554 If `None`, will be set equal to ``factor`` (i.e. equal to the factor used
555 for positive detection polarity).
559 results : `lsst.pipe.base.Struct`
560 The `~lsst.pipe.base.Struct` contains:
563 Positive detection footprints, if configured.
564 (`lsst.afw.detection.FootprintSet` or `None`)
566 Negative detection footprints, if configured.
567 (`lsst.afw.detection.FootprintSet` or `None`)
569 Multiplier for the configured threshold.
572 Multiplier for the configured threshold for negative detection polarity.
575 if factorNeg
is None:
577 self.log.info(
"Setting factor for negative detections equal to that for positive "
578 "detections: %f", factor)
579 results = pipeBase.Struct(positive=
None, negative=
None, factor=factor, factorNeg=factorNeg,
580 positiveThreshold=
None, negativeThreshold=
None)
582 if self.config.reEstimateBackground
or self.config.thresholdPolarity !=
"negative":
583 results.positiveThreshold = self.
makeThreshold(middle,
"positive", factor=factor)
586 results.positiveThreshold,
588 self.config.minPixels
590 results.positive.setRegion(bbox)
591 if self.config.reEstimateBackground
or self.config.thresholdPolarity !=
"positive":
592 results.negativeThreshold = self.
makeThreshold(middle,
"negative", factor=factorNeg)
595 results.negativeThreshold,
597 self.config.minPixels
599 results.negative.setRegion(bbox)
604 """Finalize the detected footprints.
606 Grow the footprints, set the ``DETECTED`` and ``DETECTED_NEGATIVE``
607 mask planes, and log the results.
609 ``numPos`` (number of positive footprints), ``numPosPeaks`` (number
610 of positive peaks), ``numNeg`` (number of negative footprints),
611 ``numNegPeaks`` (number of negative peaks) entries are added to the
616 mask : `lsst.afw.image.Mask`
617 Mask image on which to flag detected pixels.
618 results : `lsst.pipe.base.Struct`
619 Struct of detection results, including ``positive`` and
620 ``negative`` entries; modified.
622 Gaussian sigma of PSF.
624 Multiplier for the configured threshold. Note that this is only
625 used here for logging purposes.
626 factorNeg : `float` or `None`
627 Multiplier used for the negative detection polarity threshold.
628 If `None`, a factor equal to ``factor`` (i.e. equal to the one used
629 for positive detection polarity) is assumed. Note that this is only
630 used here for logging purposes.
632 if growOverride
is not None:
633 self.log.warning(
"config.nSigmaToGrow is set to %.2f, but the caller has set "
634 "growOverride to %.2f, so the footprints will be grown by "
635 "%.2f sigma.", self.config.nSigmaToGrow, growOverride, growOverride)
636 factorNeg = factor
if factorNeg
is None else factorNeg
637 for polarity, maskName
in ((
"positive",
"DETECTED"), (
"negative",
"DETECTED_NEGATIVE")):
638 fpSet = getattr(results, polarity)
641 if self.config.nSigmaToGrow > 0:
642 nGrow = int((self.config.nSigmaToGrow * sigma) + 0.5)
643 self.metadata[
"nGrow"] = nGrow
644 if self.config.combinedGrow:
647 stencil = (afwGeom.Stencil.CIRCLE
if self.config.isotropicGrow
else
648 afwGeom.Stencil.MANHATTAN)
650 fp.dilate(nGrow, stencil)
651 fpSet.setMask(mask, maskName)
652 if not self.config.returnOriginalFootprints:
653 setattr(results, polarity, fpSet)
656 results.numPosPeaks = 0
658 results.numNegPeaks = 0
662 if results.positive
is not None:
663 results.numPos = len(results.positive.getFootprints())
664 results.numPosPeaks = sum(len(fp.getPeaks())
for fp
in results.positive.getFootprints())
665 positive =
" %d positive peaks in %d footprints" % (results.numPosPeaks, results.numPos)
666 if results.negative
is not None:
667 results.numNeg = len(results.negative.getFootprints())
668 results.numNegPeaks = sum(len(fp.getPeaks())
for fp
in results.negative.getFootprints())
669 negative =
" %d negative peaks in %d footprints" % (results.numNegPeaks, results.numNeg)
671 self.log.info(
"Detected%s%s%s to %g +ve and %g -ve %s",
672 positive,
" and" if positive
and negative
else "", negative,
673 self.config.thresholdValue*self.config.includeThresholdMultiplier*factor,
674 self.config.thresholdValue*self.config.includeThresholdMultiplier*factorNeg,
675 "DN" if self.config.thresholdType ==
"value" else "sigma")
678 """Estimate the background after detection
682 maskedImage : `lsst.afw.image.MaskedImage`
683 Image on which to estimate the background.
684 backgrounds : `lsst.afw.math.BackgroundList`
685 List of backgrounds; modified.
686 backgroundToPhotometricRatio : `lsst.afw.image.Image`, optional
687 Image to multiply a photometrically-flattened image by to obtain a
688 background-flattened image.
689 Only used if ``config.doApplyFlatBackgroundRatio`` is ``True``.
693 bg : `lsst.afw.math.backgroundMI`
694 Empirical background model.
698 self.config.doApplyFlatBackgroundRatio,
699 backgroundToPhotometricRatio=backgroundToPhotometricRatio,
701 bg = self.background.fitBackground(maskedImage)
702 if self.config.adjustBackground:
703 self.log.warning(
"Fiddling the background by %g", self.config.adjustBackground)
704 bg += self.config.adjustBackground
705 self.log.info(
"Resubtracting the background after object detection")
706 maskedImage -= bg.getImageF(self.background.config.algorithm,
707 self.background.config.undersampleStyle)
709 actrl = bg.getBackgroundControl().getApproximateControl()
711 bg.getAsUsedUndersampleStyle(), actrl.getStyle(), actrl.getOrderX(),
712 actrl.getOrderY(), actrl.getWeighting()))
716 """Clear unwanted results from the Struct of results
718 If we specifically want only positive or only negative detections,
719 drop the ones we don't want, and its associated mask plane.
723 mask : `lsst.afw.image.Mask`
725 results : `lsst.pipe.base.Struct`
726 Detection results, with ``positive`` and ``negative`` elements;
729 if self.config.thresholdPolarity ==
"positive":
730 if self.config.reEstimateBackground:
731 mask &= ~mask.getPlaneBitMask(
"DETECTED_NEGATIVE")
732 results.negative =
None
733 elif self.config.thresholdPolarity ==
"negative":
734 if self.config.reEstimateBackground:
735 mask &= ~mask.getPlaneBitMask(
"DETECTED")
736 results.positive =
None
739 def detectFootprints(self, exposure, doSmooth=True, sigma=None, clearMask=True, expId=None,
740 background=None, backgroundToPhotometricRatio=None):
741 """Detect footprints on an exposure.
745 exposure : `lsst.afw.image.Exposure`
746 Exposure to process; DETECTED{,_NEGATIVE} mask plane will be
748 doSmooth : `bool`, optional
749 If True, smooth the image before detection using a Gaussian
750 of width ``sigma``, or the measured PSF width of ``exposure``.
751 Set to False when running on e.g. a pre-convolved image, or a mask
753 sigma : `float`, optional
754 Gaussian Sigma of PSF (pixels); used for smoothing and to grow
755 detections; if `None` then measure the sigma of the PSF of the
757 clearMask : `bool`, optional
758 Clear both DETECTED and DETECTED_NEGATIVE planes before running
760 expId : `dict`, optional
761 Exposure identifier; unused by this implementation, but used for
762 RNG seed by subclasses.
763 background : `lsst.afw.math.BackgroundList`, optional
764 Background that was already subtracted from the exposure; will be
765 modified in-place if ``reEstimateBackground=True``.
766 backgroundToPhotometricRatio : `lsst.afw.image.Image`, optional
767 Image to convert photometric-flattened image to
768 background-flattened image if ``reEstimateBackground=True`` and
769 exposure has been photometric-flattened.
773 results : `lsst.pipe.base.Struct`
774 A `~lsst.pipe.base.Struct` containing:
777 Positive polarity footprints.
778 (`lsst.afw.detection.FootprintSet` or `None`)
780 Negative polarity footprints.
781 (`lsst.afw.detection.FootprintSet` or `None`)
783 Number of footprints in positive or 0 if detection polarity was
786 Number of footprints in negative or 0 if detection polarity was
789 Re-estimated background. `None` or the input ``background``
790 if ``reEstimateBackground==False``.
791 (`lsst.afw.math.BackgroundList`)
793 Multiplication factor applied to the configured detection
796 maskedImage = exposure.maskedImage
801 psf = self.
getPsf(exposure, sigma=sigma)
803 convolveResults = self.
convolveImage(maskedImage, psf, doSmooth=doSmooth)
804 middle = convolveResults.middle
805 sigma = convolveResults.sigma
811 if self.config.doTempLocalBackground:
818 results.positive = self.
setPeakSignificance(middle, results.positive, results.positiveThreshold)
819 results.negative = self.
setPeakSignificance(middle, results.negative, results.negativeThreshold,
822 if self.config.reEstimateBackground:
826 backgroundToPhotometricRatio=backgroundToPhotometricRatio,
831 self.
display(exposure, results, middle)
836 """Set the significance of flagged pixels to zero.
840 middle : `lsst.afw.image.Exposure`
841 Score or maximum likelihood difference image.
842 The image plane will be modified in place.
844 badPixelMask = middle.mask.getPlaneBitMask(self.config.excludeMaskPlanes)
845 badPixels = middle.mask.array & badPixelMask > 0
846 middle.image.array[badPixels] = 0
849 """Set the significance of each detected peak to the pixel value divided
850 by the appropriate standard-deviation for ``config.thresholdType``.
852 Only sets significance for "stdev" and "pixel_stdev" thresholdTypes;
853 we leave it undefined for "value" and "variance" as it does not have a
854 well-defined meaning in those cases.
858 exposure : `lsst.afw.image.Exposure`
859 Exposure that footprints were detected on, likely the convolved,
860 local background-subtracted image.
861 footprints : `lsst.afw.detection.FootprintSet`
862 Footprints detected on the image.
863 threshold : `lsst.afw.detection.Threshold`
864 Threshold used to find footprints.
865 negative : `bool`, optional
866 Are we calculating for negative sources?
868 if footprints
is None or footprints.getFootprints() == []:
870 polarity = -1
if negative
else 1
874 mapper.addMinimalSchema(footprints.getFootprints()[0].peaks.schema)
875 mapper.addOutputField(
"significance", type=float,
876 doc=
"Ratio of peak value to configured standard deviation.")
882 for old, new
in zip(footprints.getFootprints(), newFootprints.getFootprints()):
884 newPeaks.extend(old.peaks, mapper=mapper)
885 new.getPeaks().clear()
886 new.setPeakCatalog(newPeaks)
889 if self.config.thresholdType ==
"pixel_stdev":
890 for footprint
in newFootprints.getFootprints():
891 footprint.updatePeakSignificance(exposure.variance, polarity)
892 elif self.config.thresholdType ==
"stdev":
893 sigma = threshold.getValue() / self.config.thresholdValue
894 for footprint
in newFootprints.getFootprints():
895 footprint.updatePeakSignificance(polarity*sigma)
897 for footprint
in newFootprints.getFootprints():
898 for peak
in footprint.peaks:
899 peak[
"significance"] = 0
904 """Make an afw.detection.Threshold object corresponding to the task's
905 configuration and the statistics of the given image.
909 image : `afw.image.MaskedImage`
910 Image to measure noise statistics from if needed.
911 thresholdParity: `str`
912 One of "positive" or "negative", to set the kind of fluctuations
913 the Threshold will detect.
915 Factor by which to multiply the configured detection threshold.
916 This is useful for tweaking the detection threshold slightly.
920 threshold : `lsst.afw.detection.Threshold`
923 parity =
False if thresholdParity ==
"negative" else True
924 thresholdValue = self.config.thresholdValue
925 thresholdType = self.config.thresholdType
926 if self.config.thresholdType ==
'stdev':
927 bad = image.getMask().getPlaneBitMask(self.config.statsMask)
929 sctrl.setAndMask(bad)
931 thresholdValue *= stats.getValue(afwMath.STDEVCLIP)
932 thresholdType =
'value'
935 threshold.setIncludeMultiplier(self.config.includeThresholdMultiplier)
936 self.log.debug(
"Detection threshold: %s", threshold)
940 """Update the Peaks in a FootprintSet by detecting new Footprints and
941 Peaks in an image and using the new Peaks instead of the old ones.
945 fpSet : `afw.detection.FootprintSet`
946 Set of Footprints whose Peaks should be updated.
947 image : `afw.image.MaskedImage`
948 Image to detect new Footprints and Peak in.
949 threshold : `afw.detection.Threshold`
950 Threshold object for detection.
952 Input Footprints with fewer Peaks than self.config.nPeaksMaxSimple
953 are not modified, and if no new Peaks are detected in an input
954 Footprint, the brightest original Peak in that Footprint is kept.
956 for footprint
in fpSet.getFootprints():
957 oldPeaks = footprint.getPeaks()
958 if len(oldPeaks) <= self.config.nPeaksMaxSimple:
963 sub = image.Factory(image, footprint.getBBox())
968 self.config.minPixels
971 for fpForPeaks
in fpSetForPeaks.getFootprints():
972 for peak
in fpForPeaks.getPeaks():
973 if footprint.contains(peak.getI()):
974 newPeaks.append(peak)
975 if len(newPeaks) > 0:
977 oldPeaks.extend(newPeaks)
983 """Set the edgeBitmask bits for all of maskedImage outside goodBBox
987 maskedImage : `lsst.afw.image.MaskedImage`
988 Image on which to set edge bits in the mask.
989 goodBBox : `lsst.geom.Box2I`
990 Bounding box of good pixels, in ``LOCAL`` coordinates.
991 edgeBitmask : `lsst.afw.image.MaskPixel`
992 Bit mask to OR with the existing mask bits in the region
993 outside ``goodBBox``.
995 msk = maskedImage.getMask()
997 mx0, my0 = maskedImage.getXY0()
998 for x0, y0, w, h
in ([0, 0,
999 msk.getWidth(), goodBBox.getBeginY() - my0],
1000 [0, goodBBox.getEndY() - my0, msk.getWidth(),
1001 maskedImage.getHeight() - (goodBBox.getEndY() - my0)],
1003 goodBBox.getBeginX() - mx0, msk.getHeight()],
1004 [goodBBox.getEndX() - mx0, 0,
1005 maskedImage.getWidth() - (goodBBox.getEndX() - mx0), msk.getHeight()],
1009 edgeMask |= edgeBitmask
1013 """Context manager for removing wide (large-scale) background
1015 Removing a wide (large-scale) background helps to suppress the
1016 detection of large footprints that may overwhelm the deblender.
1017 It does, however, set a limit on the maximum scale of objects.
1019 The background that we remove will be restored upon exit from
1020 the context manager.
1024 exposure : `lsst.afw.image.Exposure`
1025 Exposure on which to remove large-scale background.
1029 context : context manager
1030 Context manager that will ensure the temporary wide background
1033 doTempWideBackground = self.config.doTempWideBackground
1034 if doTempWideBackground:
1035 self.log.info(
"Applying temporary wide background subtraction")
1036 original = exposure.maskedImage.image.array[:].copy()
1037 self.tempWideBackground.run(exposure).background
1040 image = exposure.maskedImage.image
1041 mask = exposure.maskedImage.mask
1042 noData = mask.array & mask.getPlaneBitMask(
"NO_DATA") > 0
1043 isGood = mask.array & mask.getPlaneBitMask(self.config.statsMask) == 0
1044 image.array[noData] = np.median(image.array[~noData & isGood])
1048 if doTempWideBackground:
1049 exposure.maskedImage.image.array[:] = original
1052def addExposures(exposureList):
1053 """Add a set of exposures together.
1057 exposureList : `list` of `lsst.afw.image.Exposure`
1058 Sequence of exposures to add.
1062 addedExposure : `lsst.afw.image.Exposure`
1063 An exposure of the same size as each exposure in ``exposureList``,
1064 with the metadata from ``exposureList[0]`` and a masked image equal
1065 to the sum of all the exposure's masked images.
1067 exposure0 = exposureList[0]
1068 image0 = exposure0.getMaskedImage()
1070 addedImage = image0.Factory(image0,
True)
1071 addedImage.setXY0(image0.getXY0())
1073 for exposure
in exposureList[1:]:
1074 image = exposure.getMaskedImage()
1077 addedExposure = exposure0.Factory(addedImage, exposure0.getWcs())
1078 return addedExposure
A circularly symmetric Gaussian Psf class with no spatial variation, intended mostly for testing purp...
Parameters to control convolution.
A kernel described by a pair of functions: func(x, y) = colFunc(x) * rowFunc(y)
Pass parameters to a Statistics object.
A mapping between the keys of two Schemas, used to copy data between them.
An integer coordinate rectangle.
makeThreshold(self, image, thresholdParity, factor=1.0)
applyTempLocalBackground(self, exposure, middle, results)
calculateKernelSize(self, sigma)
detectFootprints(self, exposure, doSmooth=True, sigma=None, clearMask=True, expId=None, background=None, backgroundToPhotometricRatio=None)
setEdgeBits(maskedImage, goodBBox, edgeBitmask)
tempWideBackgroundContext(self, exposure)
setPeakSignificance(self, exposure, footprints, threshold, negative=False)
clearUnwantedResults(self, mask, results)
updatePeaks(self, fpSet, image, threshold)
getPsf(self, exposure, sigma=None)
applyThreshold(self, middle, bbox, factor=1.0, factorNeg=None)
convolveImage(self, maskedImage, psf, doSmooth=True)
display(self, exposure, results, convolvedImage=None)
removeBadPixels(self, middle)
__init__(self, schema=None, **kwds)
finalizeFootprints(self, mask, results, sigma, factor=1.0, factorNeg=None, growOverride=None)
run(self, table, exposure, doSmooth=True, sigma=None, clearMask=True, expId=None, background=None, backgroundToPhotometricRatio=None)
reEstimateBackground(self, maskedImage, backgrounds, backgroundToPhotometricRatio=None)
Threshold createThreshold(const double value, const std::string type="value", const bool polarity=true)
Factory method for creating Threshold objects.
Statistics makeStatistics(lsst::afw::image::Image< Pixel > const &img, lsst::afw::image::Mask< image::MaskPixel > const &msk, int const flags, StatisticsControl const &sctrl=StatisticsControl())
Handle a watered-down front-end to the constructor (no variance)
void convolve(OutImageT &convolvedImage, InImageT const &inImage, KernelT const &kernel, ConvolutionControl const &convolutionControl=ConvolutionControl())
Convolve an Image or MaskedImage with a Kernel, setting pixels of an existing output image.
backgroundFlatContext(maskedImage, doApply, backgroundToPhotometricRatio=None)