24__all__ = (
"SourceDetectionConfig",
"SourceDetectionTask",
"addExposures")
26from contextlib
import contextmanager
39from lsst.utils.timer
import timeMethod
40from .subtractBackground
import SubtractBackgroundTask
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 background = pexConfig.ConfigurableField(
107 doc=
"Background re-estimation; ignored if reEstimateBackground false",
108 target=SubtractBackgroundTask,
110 tempLocalBackground = pexConfig.ConfigurableField(
111 doc=(
"A local (small-scale), temporary background estimation step run between "
112 "detecting above-threshold regions and detecting the peaks within "
113 "them; used to avoid detecting spuerious peaks in the wings."),
114 target=SubtractBackgroundTask,
116 doTempLocalBackground = pexConfig.Field(
118 doc=
"Enable temporary local background subtraction? (see tempLocalBackground)",
121 tempWideBackground = pexConfig.ConfigurableField(
122 doc=(
"A wide (large-scale) background estimation and removal before footprint and peak detection. "
123 "It is added back into the image after detection. The purpose is to suppress very large "
124 "footprints (e.g., from large artifacts) that the deblender may choke on."),
125 target=SubtractBackgroundTask,
127 doTempWideBackground = pexConfig.Field(
129 doc=
"Do temporary wide (large-scale) background subtraction before footprint detection?",
132 nPeaksMaxSimple = pexConfig.Field(
134 doc=(
"The maximum number of peaks in a Footprint before trying to "
135 "replace its peaks using the temporary local background"),
138 nSigmaForKernel = pexConfig.Field(
140 doc=(
"Multiple of PSF RMS size to use for convolution kernel bounding box size; "
141 "note that this is not a half-size. The size will be rounded up to the nearest odd integer"),
144 statsMask = pexConfig.ListField(
146 doc=
"Mask planes to ignore when calculating statistics of image (for thresholdType=stdev)",
147 default=[
'BAD',
'SAT',
'EDGE',
'NO_DATA'],
149 excludeMaskPlanes = lsst.pex.config.ListField(
152 doc=
"Mask planes to exclude when detecting sources."
165 for maskPlane
in (
"DETECTED",
"DETECTED_NEGATIVE"):
171 """Detect peaks and footprints of sources in an image.
173 This task convolves the image with a Gaussian approximation to the PSF,
174 matched to the sigma of the input exposure, because this is separable and
175 fast. The PSF would have to be very non-Gaussian or non-circular for this
176 approximation to have a significant impact on the signal-to-noise of the
181 schema : `lsst.afw.table.Schema`
182 Schema object used to create the output `lsst.afw.table.SourceCatalog`
184 Keyword arguments passed to `lsst.pipe.base.Task.__init__`
186 If schema is not None and configured for 'both' detections,
187 a 'flags.negative' field will be added to label detections made with a
192 This task can add fields to the schema, so any code calling this task must
193 ensure that these columns are indeed present in the input match list.
195 ConfigClass = SourceDetectionConfig
196 _DefaultName =
"sourceDetection"
199 pipeBase.Task.__init__(self, **kwds)
200 if schema
is not None and self.config.thresholdPolarity ==
"both":
202 "flags_negative", type=
"Flag",
203 doc=
"set if source was detected as significantly negative"
206 if self.config.thresholdPolarity ==
"both":
207 self.log.warning(
"Detection polarity set to 'both', but no flag will be "
208 "set to distinguish between positive and negative detections")
210 if self.config.reEstimateBackground:
211 self.makeSubtask(
"background")
212 if self.config.doTempLocalBackground:
213 self.makeSubtask(
"tempLocalBackground")
214 if self.config.doTempWideBackground:
215 self.makeSubtask(
"tempWideBackground")
218 def run(self, table, exposure, doSmooth=True, sigma=None, clearMask=True, expId=None,
220 r"""Detect sources and return catalog(s) of detections.
224 table : `lsst.afw.table.SourceTable`
225 Table object that will be used to create the SourceCatalog.
226 exposure : `lsst.afw.image.Exposure`
227 Exposure to process; DETECTED mask plane will be set in-place.
228 doSmooth : `bool`, optional
229 If True, smooth the image before detection using a Gaussian of width
230 ``sigma``, or the measured PSF width. Set to False when running on
231 e.g. a pre-convolved image, or a mask plane.
232 sigma : `float`, optional
233 Sigma of PSF (pixels); used for smoothing and to grow detections;
234 if None then measure the sigma of the PSF of the exposure
235 clearMask : `bool`, optional
236 Clear DETECTED{,_NEGATIVE} planes before running detection.
237 expId : `int`, optional
238 Exposure identifier; unused by this implementation, but used for
239 RNG seed by subclasses.
240 background : `lsst.afw.math.BackgroundList`, optional
241 Background that was already subtracted from the exposure; will be
242 modified in-place if ``reEstimateBackground=True``.
246 result : `lsst.pipe.base.Struct`
247 The `~lsst.pipe.base.Struct` contains:
250 Detected sources on the exposure.
251 (`lsst.afw.table.SourceCatalog`)
253 Positive polarity footprints.
254 (`lsst.afw.detection.FootprintSet` or `None`)
256 Negative polarity footprints.
257 (`lsst.afw.detection.FootprintSet` or `None`)
259 Number of footprints in positive or 0 if detection polarity was
262 Number of footprints in negative or 0 if detection polarity was
265 Re-estimated background. `None` if
266 ``reEstimateBackground==False``.
267 (`lsst.afw.math.BackgroundList`)
269 Multiplication factor applied to the configured detection
275 Raised if flags.negative is needed, but isn't in table's schema.
276 lsst.pipe.base.TaskError
277 Raised if sigma=None, doSmooth=True and the exposure has no PSF.
281 If you want to avoid dealing with Sources and Tables, you can use
282 `detectFootprints()` to just get the
283 `~lsst.afw.detection.FootprintSet`\s.
286 raise ValueError(
"Table has incorrect Schema")
287 results = self.
detectFootprints(exposure=exposure, doSmooth=doSmooth, sigma=sigma,
288 clearMask=clearMask, expId=expId, background=background)
289 sources = afwTable.SourceCatalog(table)
290 sources.reserve(results.numPos + results.numNeg)
292 results.negative.makeSources(sources)
294 for record
in sources:
297 results.positive.makeSources(sources)
298 results.sources = sources
301 def display(self, exposure, results, convolvedImage=None):
302 """Display detections if so configured
304 Displays the ``exposure`` in frame 0, overlays the detection peaks.
306 Requires that ``lsstDebug`` has been set up correctly, so that
307 ``lsstDebug.Info("lsst.meas.algorithms.detection")`` evaluates `True`.
309 If the ``convolvedImage`` is non-`None` and
310 ``lsstDebug.Info("lsst.meas.algorithms.detection") > 1``, the
311 ``convolvedImage`` will be displayed in frame 1.
315 exposure : `lsst.afw.image.Exposure`
316 Exposure to display, on which will be plotted the detections.
317 results : `lsst.pipe.base.Struct`
318 Results of the 'detectFootprints' method, containing positive and
319 negative footprints (which contain the peak positions that we will
320 plot). This is a `Struct` with ``positive`` and ``negative``
321 elements that are of type `lsst.afw.detection.FootprintSet`.
322 convolvedImage : `lsst.afw.image.Image`, optional
323 Convolved image used for thresholding.
336 afwDisplay.setDefaultMaskTransparency(75)
338 disp0 = afwDisplay.Display(frame=0)
339 disp0.mtv(exposure, title=
"detection")
341 def plotPeaks(fps, ctype):
344 with disp0.Buffering():
345 for fp
in fps.getFootprints():
346 for pp
in fp.getPeaks():
347 disp0.dot(
"+", pp.getFx(), pp.getFy(), ctype=ctype)
348 plotPeaks(results.positive,
"yellow")
349 plotPeaks(results.negative,
"red")
351 if convolvedImage
and display > 1:
352 disp1 = afwDisplay.Display(frame=1)
353 disp1.mtv(convolvedImage, title=
"PSF smoothed")
355 disp2 = afwDisplay.Display(frame=2)
356 disp2.mtv(afwImage.ImageF(np.sqrt(exposure.variance.array)), title=
"stddev")
359 """Apply a temporary local background subtraction
361 This temporary local background serves to suppress noise fluctuations
362 in the wings of bright objects.
364 Peaks in the footprints will be updated.
368 exposure : `lsst.afw.image.Exposure`
369 Exposure for which to fit local background.
370 middle : `lsst.afw.image.MaskedImage`
371 Convolved image on which detection will be performed
372 (typically smaller than ``exposure`` because the
373 half-kernel has been removed around the edges).
374 results : `lsst.pipe.base.Struct`
375 Results of the 'detectFootprints' method, containing positive and
376 negative footprints (which contain the peak positions that we will
377 plot). This is a `Struct` with ``positive`` and ``negative``
378 elements that are of type `lsst.afw.detection.FootprintSet`.
383 bg = self.tempLocalBackground.fitBackground(exposure.getMaskedImage())
384 bgImage = bg.getImageF(self.tempLocalBackground.config.algorithm,
385 self.tempLocalBackground.config.undersampleStyle)
386 middle -= bgImage.Factory(bgImage, middle.getBBox())
387 if self.config.thresholdPolarity !=
"negative":
388 results.positiveThreshold = self.
makeThreshold(middle,
"positive")
389 self.
updatePeaks(results.positive, middle, results.positiveThreshold)
390 if self.config.thresholdPolarity !=
"positive":
391 results.negativeThreshold = self.
makeThreshold(middle,
"negative")
392 self.
updatePeaks(results.negative, middle, results.negativeThreshold)
395 """Clear the DETECTED and DETECTED_NEGATIVE mask planes.
397 Removes any previous detection mask in preparation for a new
402 mask : `lsst.afw.image.Mask`
405 mask &= ~(mask.getPlaneBitMask(
"DETECTED") | mask.getPlaneBitMask(
"DETECTED_NEGATIVE"))
408 """Calculate the size of the smoothing kernel.
410 Uses the ``nSigmaForKernel`` configuration parameter. Note
411 that that is the full width of the kernel bounding box
412 (so a value of 7 means 3.5 sigma on either side of center).
413 The value will be rounded up to the nearest odd integer.
418 Gaussian sigma of smoothing kernel.
423 Size of the smoothing kernel.
425 return (int(sigma * self.config.nSigmaForKernel + 0.5)//2)*2 + 1
428 """Create a single Gaussian PSF for an exposure.
430 If ``sigma`` is provided, we make a `~lsst.afw.detection.GaussianPsf`
431 with that, otherwise use the sigma from the psf of the ``exposure`` to
432 make the `~lsst.afw.detection.GaussianPsf`.
436 exposure : `lsst.afw.image.Exposure`
437 Exposure from which to retrieve the PSF.
438 sigma : `float`, optional
439 Gaussian sigma to use if provided.
443 psf : `lsst.afw.detection.GaussianPsf`
444 PSF to use for detection.
449 Raised if ``sigma`` is not provided and ``exposure`` does not
450 contain a ``Psf`` object.
453 psf = exposure.getPsf()
455 raise RuntimeError(
"Unable to determine PSF to use for detection: no sigma provided")
456 sigma = psf.computeShape(psf.getAveragePosition()).getDeterminantRadius()
458 psf = afwDet.GaussianPsf(size, size, sigma)
462 """Convolve the image with the PSF.
464 We convolve the image with a Gaussian approximation to the PSF,
465 because this is separable and therefore fast. It's technically a
466 correlation rather than a convolution, but since we use a symmetric
467 Gaussian there's no difference.
469 The convolution can be disabled with ``doSmooth=False``. If we do
470 convolve, we mask the edges as ``EDGE`` and return the convolved image
471 with the edges removed. This is because we can't convolve the edges
472 because the kernel would extend off the image.
476 maskedImage : `lsst.afw.image.MaskedImage`
478 psf : `lsst.afw.detection.Psf`
479 PSF to convolve with (actually with a Gaussian approximation
482 Actually do the convolution? Set to False when running on
483 e.g. a pre-convolved image, or a mask plane.
487 results : `lsst.pipe.base.Struct`
488 The `~lsst.pipe.base.Struct` contains:
491 Convolved image, without the edges. (`lsst.afw.image.MaskedImage`)
493 Gaussian sigma used for the convolution. (`float`)
495 self.metadata[
"doSmooth"] = doSmooth
496 sigma = psf.computeShape(psf.getAveragePosition()).getDeterminantRadius()
497 self.metadata[
"sigma"] = sigma
500 middle = maskedImage.Factory(maskedImage, deep=
True)
501 return pipeBase.Struct(middle=middle, sigma=sigma)
506 self.metadata[
"smoothingKernelWidth"] = kWidth
507 gaussFunc = afwMath.GaussianFunction1D(sigma)
508 gaussKernel = afwMath.SeparableKernel(kWidth, kWidth, gaussFunc, gaussFunc)
510 convolvedImage = maskedImage.Factory(maskedImage.getBBox())
512 afwMath.convolve(convolvedImage, maskedImage, gaussKernel, afwMath.ConvolutionControl())
515 goodBBox = gaussKernel.shrinkBBox(convolvedImage.getBBox())
516 middle = convolvedImage.Factory(convolvedImage, goodBBox, afwImage.PARENT,
False)
519 self.
setEdgeBits(maskedImage, goodBBox, maskedImage.getMask().getPlaneBitMask(
"EDGE"))
521 return pipeBase.Struct(middle=middle, sigma=sigma)
524 r"""Apply thresholds to the convolved image
526 Identifies `~lsst.afw.detection.Footprint`\s, both positive and negative.
527 The threshold can be modified by the provided multiplication
532 middle : `lsst.afw.image.MaskedImage`
533 Convolved image to threshold.
534 bbox : `lsst.geom.Box2I`
535 Bounding box of unconvolved image.
537 Multiplier for the configured threshold.
538 factorNeg : `float` or `None`
539 Multiplier for the configured threshold for negative detection polarity.
540 If `None`, will be set equal to ``factor`` (i.e. equal to the factor used
541 for positive detection polarity).
545 results : `lsst.pipe.base.Struct`
546 The `~lsst.pipe.base.Struct` contains:
549 Positive detection footprints, if configured.
550 (`lsst.afw.detection.FootprintSet` or `None`)
552 Negative detection footprints, if configured.
553 (`lsst.afw.detection.FootprintSet` or `None`)
555 Multiplier for the configured threshold.
558 Multiplier for the configured threshold for negative detection polarity.
561 if factorNeg
is None:
563 self.log.info(
"Setting factor for negative detections equal to that for positive "
564 "detections: %f", factor)
565 results = pipeBase.Struct(positive=
None, negative=
None, factor=factor, factorNeg=factorNeg,
566 positiveThreshold=
None, negativeThreshold=
None)
568 if self.config.reEstimateBackground
or self.config.thresholdPolarity !=
"negative":
569 results.positiveThreshold = self.
makeThreshold(middle,
"positive", factor=factor)
570 results.positive = afwDet.FootprintSet(
572 results.positiveThreshold,
574 self.config.minPixels
576 results.positive.setRegion(bbox)
577 if self.config.reEstimateBackground
or self.config.thresholdPolarity !=
"positive":
578 results.negativeThreshold = self.
makeThreshold(middle,
"negative", factor=factorNeg)
579 results.negative = afwDet.FootprintSet(
581 results.negativeThreshold,
583 self.config.minPixels
585 results.negative.setRegion(bbox)
590 """Finalize the detected footprints.
592 Grow the footprints, set the ``DETECTED`` and ``DETECTED_NEGATIVE``
593 mask planes, and log the results.
595 ``numPos`` (number of positive footprints), ``numPosPeaks`` (number
596 of positive peaks), ``numNeg`` (number of negative footprints),
597 ``numNegPeaks`` (number of negative peaks) entries are added to the
602 mask : `lsst.afw.image.Mask`
603 Mask image on which to flag detected pixels.
604 results : `lsst.pipe.base.Struct`
605 Struct of detection results, including ``positive`` and
606 ``negative`` entries; modified.
608 Gaussian sigma of PSF.
610 Multiplier for the configured threshold. Note that this is only
611 used here for logging purposes.
612 factorNeg : `float` or `None`
613 Multiplier used for the negative detection polarity threshold.
614 If `None`, a factor equal to ``factor`` (i.e. equal to the one used
615 for positive detection polarity) is assumed. Note that this is only
616 used here for logging purposes.
618 factorNeg = factor
if factorNeg
is None else factorNeg
619 for polarity, maskName
in ((
"positive",
"DETECTED"), (
"negative",
"DETECTED_NEGATIVE")):
620 fpSet = getattr(results, polarity)
623 if self.config.nSigmaToGrow > 0:
624 nGrow = int((self.config.nSigmaToGrow * sigma) + 0.5)
625 self.metadata[
"nGrow"] = nGrow
626 if self.config.combinedGrow:
627 fpSet = afwDet.FootprintSet(fpSet, nGrow, self.config.isotropicGrow)
629 stencil = (afwGeom.Stencil.CIRCLE
if self.config.isotropicGrow
else
630 afwGeom.Stencil.MANHATTAN)
632 fp.dilate(nGrow, stencil)
633 fpSet.setMask(mask, maskName)
634 if not self.config.returnOriginalFootprints:
635 setattr(results, polarity, fpSet)
638 results.numPosPeaks = 0
640 results.numNegPeaks = 0
644 if results.positive
is not None:
645 results.numPos = len(results.positive.getFootprints())
646 results.numPosPeaks = sum(len(fp.getPeaks())
for fp
in results.positive.getFootprints())
647 positive =
" %d positive peaks in %d footprints" % (results.numPosPeaks, results.numPos)
648 if results.negative
is not None:
649 results.numNeg = len(results.negative.getFootprints())
650 results.numNegPeaks = sum(len(fp.getPeaks())
for fp
in results.negative.getFootprints())
651 negative =
" %d negative peaks in %d footprints" % (results.numNegPeaks, results.numNeg)
653 self.log.info(
"Detected%s%s%s to %g +ve and %g -ve %s",
654 positive,
" and" if positive
and negative
else "", negative,
655 self.config.thresholdValue*self.config.includeThresholdMultiplier*factor,
656 self.config.thresholdValue*self.config.includeThresholdMultiplier*factorNeg,
657 "DN" if self.config.thresholdType ==
"value" else "sigma")
660 """Estimate the background after detection
664 maskedImage : `lsst.afw.image.MaskedImage`
665 Image on which to estimate the background.
666 backgrounds : `lsst.afw.math.BackgroundList`
667 List of backgrounds; modified.
671 bg : `lsst.afw.math.backgroundMI`
672 Empirical background model.
674 bg = self.background.fitBackground(maskedImage)
675 if self.config.adjustBackground:
676 self.log.warning(
"Fiddling the background by %g", self.config.adjustBackground)
677 bg += self.config.adjustBackground
678 self.log.info(
"Resubtracting the background after object detection")
679 maskedImage -= bg.getImageF(self.background.config.algorithm,
680 self.background.config.undersampleStyle)
682 actrl = bg.getBackgroundControl().getApproximateControl()
683 backgrounds.append((bg, getattr(afwMath.Interpolate, self.background.config.algorithm),
684 bg.getAsUsedUndersampleStyle(), actrl.getStyle(), actrl.getOrderX(),
685 actrl.getOrderY(), actrl.getWeighting()))
689 """Clear unwanted results from the Struct of results
691 If we specifically want only positive or only negative detections,
692 drop the ones we don't want, and its associated mask plane.
696 mask : `lsst.afw.image.Mask`
698 results : `lsst.pipe.base.Struct`
699 Detection results, with ``positive`` and ``negative`` elements;
702 if self.config.thresholdPolarity ==
"positive":
703 if self.config.reEstimateBackground:
704 mask &= ~mask.getPlaneBitMask(
"DETECTED_NEGATIVE")
705 results.negative =
None
706 elif self.config.thresholdPolarity ==
"negative":
707 if self.config.reEstimateBackground:
708 mask &= ~mask.getPlaneBitMask(
"DETECTED")
709 results.positive =
None
712 def detectFootprints(self, exposure, doSmooth=True, sigma=None, clearMask=True, expId=None,
714 """Detect footprints on an exposure.
718 exposure : `lsst.afw.image.Exposure`
719 Exposure to process; DETECTED{,_NEGATIVE} mask plane will be
721 doSmooth : `bool`, optional
722 If True, smooth the image before detection using a Gaussian
723 of width ``sigma``, or the measured PSF width of ``exposure``.
724 Set to False when running on e.g. a pre-convolved image, or a mask
726 sigma : `float`, optional
727 Gaussian Sigma of PSF (pixels); used for smoothing and to grow
728 detections; if `None` then measure the sigma of the PSF of the
730 clearMask : `bool`, optional
731 Clear both DETECTED and DETECTED_NEGATIVE planes before running
733 expId : `dict`, optional
734 Exposure identifier; unused by this implementation, but used for
735 RNG seed by subclasses.
736 background : `lsst.afw.math.BackgroundList`, optional
737 Background that was already subtracted from the exposure; will be
738 modified in-place if ``reEstimateBackground=True``.
742 results : `lsst.pipe.base.Struct`
743 A `~lsst.pipe.base.Struct` containing:
746 Positive polarity footprints.
747 (`lsst.afw.detection.FootprintSet` or `None`)
749 Negative polarity footprints.
750 (`lsst.afw.detection.FootprintSet` or `None`)
752 Number of footprints in positive or 0 if detection polarity was
755 Number of footprints in negative or 0 if detection polarity was
758 Re-estimated background. `None` or the input ``background``
759 if ``reEstimateBackground==False``.
760 (`lsst.afw.math.BackgroundList`)
762 Multiplication factor applied to the configured detection
765 maskedImage = exposure.maskedImage
770 psf = self.
getPsf(exposure, sigma=sigma)
772 convolveResults = self.
convolveImage(maskedImage, psf, doSmooth=doSmooth)
773 middle = convolveResults.middle
774 sigma = convolveResults.sigma
778 results.background = background
if background
is not None else afwMath.BackgroundList()
780 if self.config.doTempLocalBackground:
787 results.positive = self.
setPeakSignificance(middle, results.positive, results.positiveThreshold)
788 results.negative = self.
setPeakSignificance(middle, results.negative, results.negativeThreshold,
791 if self.config.reEstimateBackground:
796 self.
display(exposure, results, middle)
801 """Set the significance of flagged pixels to zero.
805 middle : `lsst.afw.image.ExposureF`
806 Score or maximum likelihood difference image.
807 The image plane will be modified in place.
810 badPixels = middle.mask.array & badPixelMask > 0
811 middle.image.array[badPixels] = 0
814 """Set the significance of each detected peak to the pixel value divided
815 by the appropriate standard-deviation for ``config.thresholdType``.
817 Only sets significance for "stdev" and "pixel_stdev" thresholdTypes;
818 we leave it undefined for "value" and "variance" as it does not have a
819 well-defined meaning in those cases.
823 exposure : `lsst.afw.image.Exposure`
824 Exposure that footprints were detected on, likely the convolved,
825 local background-subtracted image.
826 footprints : `lsst.afw.detection.FootprintSet`
827 Footprints detected on the image.
828 threshold : `lsst.afw.detection.Threshold`
829 Threshold used to find footprints.
830 negative : `bool`, optional
831 Are we calculating for negative sources?
833 if footprints
is None or footprints.getFootprints() == []:
835 polarity = -1
if negative
else 1
838 mapper = afwTable.SchemaMapper(footprints.getFootprints()[0].peaks.schema)
839 mapper.addMinimalSchema(footprints.getFootprints()[0].peaks.schema)
840 mapper.addOutputField(
"significance", type=float,
841 doc=
"Ratio of peak value to configured standard deviation.")
846 newFootprints = afwDet.FootprintSet(footprints)
847 for old, new
in zip(footprints.getFootprints(), newFootprints.getFootprints()):
848 newPeaks = afwDet.PeakCatalog(mapper.getOutputSchema())
849 newPeaks.extend(old.peaks, mapper=mapper)
850 new.getPeaks().clear()
851 new.setPeakCatalog(newPeaks)
854 if self.config.thresholdType ==
"pixel_stdev":
855 for footprint
in newFootprints.getFootprints():
856 footprint.updatePeakSignificance(exposure.variance, polarity)
857 elif self.config.thresholdType ==
"stdev":
858 sigma = threshold.getValue() / self.config.thresholdValue
859 for footprint
in newFootprints.getFootprints():
860 footprint.updatePeakSignificance(polarity*sigma)
862 for footprint
in newFootprints.getFootprints():
863 for peak
in footprint.peaks:
864 peak[
"significance"] = 0
869 """Make an afw.detection.Threshold object corresponding to the task's
870 configuration and the statistics of the given image.
874 image : `afw.image.MaskedImage`
875 Image to measure noise statistics from if needed.
876 thresholdParity: `str`
877 One of "positive" or "negative", to set the kind of fluctuations
878 the Threshold will detect.
880 Factor by which to multiply the configured detection threshold.
881 This is useful for tweaking the detection threshold slightly.
885 threshold : `lsst.afw.detection.Threshold`
888 parity =
False if thresholdParity ==
"negative" else True
889 thresholdValue = self.config.thresholdValue
890 thresholdType = self.config.thresholdType
891 if self.config.thresholdType ==
'stdev':
892 bad = image.getMask().getPlaneBitMask(self.config.statsMask)
893 sctrl = afwMath.StatisticsControl()
894 sctrl.setAndMask(bad)
895 stats = afwMath.makeStatistics(image, afwMath.STDEVCLIP, sctrl)
896 thresholdValue *= stats.getValue(afwMath.STDEVCLIP)
897 thresholdType =
'value'
899 threshold = afwDet.createThreshold(thresholdValue*factor, thresholdType, parity)
900 threshold.setIncludeMultiplier(self.config.includeThresholdMultiplier)
901 self.log.debug(
"Detection threshold: %s", threshold)
905 """Update the Peaks in a FootprintSet by detecting new Footprints and
906 Peaks in an image and using the new Peaks instead of the old ones.
910 fpSet : `afw.detection.FootprintSet`
911 Set of Footprints whose Peaks should be updated.
912 image : `afw.image.MaskedImage`
913 Image to detect new Footprints and Peak in.
914 threshold : `afw.detection.Threshold`
915 Threshold object for detection.
917 Input Footprints with fewer Peaks than self.config.nPeaksMaxSimple
918 are not modified, and if no new Peaks are detected in an input
919 Footprint, the brightest original Peak in that Footprint is kept.
921 for footprint
in fpSet.getFootprints():
922 oldPeaks = footprint.getPeaks()
923 if len(oldPeaks) <= self.config.nPeaksMaxSimple:
928 sub = image.Factory(image, footprint.getBBox())
929 fpSetForPeaks = afwDet.FootprintSet(
933 self.config.minPixels
935 newPeaks = afwDet.PeakCatalog(oldPeaks.getTable())
936 for fpForPeaks
in fpSetForPeaks.getFootprints():
937 for peak
in fpForPeaks.getPeaks():
938 if footprint.contains(peak.getI()):
939 newPeaks.append(peak)
940 if len(newPeaks) > 0:
942 oldPeaks.extend(newPeaks)
948 """Set the edgeBitmask bits for all of maskedImage outside goodBBox
952 maskedImage : `lsst.afw.image.MaskedImage`
953 Image on which to set edge bits in the mask.
954 goodBBox : `lsst.geom.Box2I`
955 Bounding box of good pixels, in ``LOCAL`` coordinates.
956 edgeBitmask : `lsst.afw.image.MaskPixel`
957 Bit mask to OR with the existing mask bits in the region
958 outside ``goodBBox``.
960 msk = maskedImage.getMask()
962 mx0, my0 = maskedImage.getXY0()
963 for x0, y0, w, h
in ([0, 0,
964 msk.getWidth(), goodBBox.getBeginY() - my0],
965 [0, goodBBox.getEndY() - my0, msk.getWidth(),
966 maskedImage.getHeight() - (goodBBox.getEndY() - my0)],
968 goodBBox.getBeginX() - mx0, msk.getHeight()],
969 [goodBBox.getEndX() - mx0, 0,
970 maskedImage.getWidth() - (goodBBox.getEndX() - mx0), msk.getHeight()],
974 edgeMask |= edgeBitmask
978 """Context manager for removing wide (large-scale) background
980 Removing a wide (large-scale) background helps to suppress the
981 detection of large footprints that may overwhelm the deblender.
982 It does, however, set a limit on the maximum scale of objects.
984 The background that we remove will be restored upon exit from
989 exposure : `lsst.afw.image.Exposure`
990 Exposure on which to remove large-scale background.
994 context : context manager
995 Context manager that will ensure the temporary wide background
998 doTempWideBackground = self.config.doTempWideBackground
999 if doTempWideBackground:
1000 self.log.info(
"Applying temporary wide background subtraction")
1001 original = exposure.maskedImage.image.array[:].copy()
1002 self.tempWideBackground.
run(exposure).background
1005 image = exposure.maskedImage.image
1006 mask = exposure.maskedImage.mask
1007 noData = mask.array & mask.getPlaneBitMask(
"NO_DATA") > 0
1008 isGood = mask.array & mask.getPlaneBitMask(self.config.statsMask) == 0
1009 image.array[noData] = np.median(image.array[~noData & isGood])
1013 if doTempWideBackground:
1014 exposure.maskedImage.image.array[:] = original
1018 """Add a set of exposures together.
1022 exposureList : `list` of `lsst.afw.image.Exposure`
1023 Sequence of exposures to add.
1027 addedExposure : `lsst.afw.image.Exposure`
1028 An exposure of the same size as each exposure in ``exposureList``,
1029 with the metadata from ``exposureList[0]`` and a masked image equal
1030 to the sum of all the exposure's masked images.
1032 exposure0 = exposureList[0]
1033 image0 = exposure0.getMaskedImage()
1035 addedImage = image0.Factory(image0,
True)
1036 addedImage.setXY0(image0.getXY0())
1038 for exposure
in exposureList[1:]:
1039 image = exposure.getMaskedImage()
1042 addedExposure = exposure0.Factory(addedImage, exposure0.getWcs())
1043 return addedExposure
static MaskPixelT getPlaneBitMask(const std::vector< std::string > &names)
makeThreshold(self, image, thresholdParity, factor=1.0)
applyTempLocalBackground(self, exposure, middle, results)
calculateKernelSize(self, sigma)
run(self, table, exposure, doSmooth=True, sigma=None, clearMask=True, expId=None, background=None)
setEdgeBits(maskedImage, goodBBox, edgeBitmask)
tempWideBackgroundContext(self, exposure)
setPeakSignificance(self, exposure, footprints, threshold, negative=False)
clearUnwantedResults(self, mask, results)
detectFootprints(self, exposure, doSmooth=True, sigma=None, clearMask=True, expId=None, background=None)
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)
finalizeFootprints(self, mask, results, sigma, factor=1.0, factorNeg=None)
removeBadPixels(self, middle)
__init__(self, schema=None, **kwds)
reEstimateBackground(self, maskedImage, backgrounds)
addExposures(exposureList)