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
227 def run(self, table, exposure, doSmooth=True, sigma=None, clearMask=True, expId=None,
…
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")
315 def display(self, exposure, results, convolvedImage=None):
…
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 factorNeg = factor
if factorNeg
is None else factorNeg
633 for polarity, maskName
in ((
"positive",
"DETECTED"), (
"negative",
"DETECTED_NEGATIVE")):
634 fpSet = getattr(results, polarity)
637 if self.config.nSigmaToGrow > 0:
638 nGrow = int((self.config.nSigmaToGrow * sigma) + 0.5)
639 self.metadata[
"nGrow"] = nGrow
640 if self.config.combinedGrow:
643 stencil = (afwGeom.Stencil.CIRCLE
if self.config.isotropicGrow
else
644 afwGeom.Stencil.MANHATTAN)
646 fp.dilate(nGrow, stencil)
647 fpSet.setMask(mask, maskName)
648 if not self.config.returnOriginalFootprints:
649 setattr(results, polarity, fpSet)
652 results.numPosPeaks = 0
654 results.numNegPeaks = 0
658 if results.positive
is not None:
659 results.numPos = len(results.positive.getFootprints())
660 results.numPosPeaks = sum(len(fp.getPeaks())
for fp
in results.positive.getFootprints())
661 positive =
" %d positive peaks in %d footprints" % (results.numPosPeaks, results.numPos)
662 if results.negative
is not None:
663 results.numNeg = len(results.negative.getFootprints())
664 results.numNegPeaks = sum(len(fp.getPeaks())
for fp
in results.negative.getFootprints())
665 negative =
" %d negative peaks in %d footprints" % (results.numNegPeaks, results.numNeg)
667 self.log.info(
"Detected%s%s%s to %g +ve and %g -ve %s",
668 positive,
" and" if positive
and negative
else "", negative,
669 self.config.thresholdValue*self.config.includeThresholdMultiplier*factor,
670 self.config.thresholdValue*self.config.includeThresholdMultiplier*factorNeg,
671 "DN" if self.config.thresholdType ==
"value" else "sigma")
674 """Estimate the background after detection
678 maskedImage : `lsst.afw.image.MaskedImage`
679 Image on which to estimate the background.
680 backgrounds : `lsst.afw.math.BackgroundList`
681 List of backgrounds; modified.
682 backgroundToPhotometricRatio : `lsst.afw.image.Image`, optional
683 Image to multiply a photometrically-flattened image by to obtain a
684 background-flattened image.
685 Only used if ``config.doApplyFlatBackgroundRatio`` is ``True``.
689 bg : `lsst.afw.math.backgroundMI`
690 Empirical background model.
694 self.config.doApplyFlatBackgroundRatio,
695 backgroundToPhotometricRatio=backgroundToPhotometricRatio,
697 bg = self.background.fitBackground(maskedImage)
698 if self.config.adjustBackground:
699 self.log.warning(
"Fiddling the background by %g", self.config.adjustBackground)
700 bg += self.config.adjustBackground
701 self.log.info(
"Resubtracting the background after object detection")
702 maskedImage -= bg.getImageF(self.background.config.algorithm,
703 self.background.config.undersampleStyle)
705 actrl = bg.getBackgroundControl().getApproximateControl()
707 bg.getAsUsedUndersampleStyle(), actrl.getStyle(), actrl.getOrderX(),
708 actrl.getOrderY(), actrl.getWeighting()))
712 """Clear unwanted results from the Struct of results
714 If we specifically want only positive or only negative detections,
715 drop the ones we don't want, and its associated mask plane.
719 mask : `lsst.afw.image.Mask`
721 results : `lsst.pipe.base.Struct`
722 Detection results, with ``positive`` and ``negative`` elements;
725 if self.config.thresholdPolarity ==
"positive":
726 if self.config.reEstimateBackground:
727 mask &= ~mask.getPlaneBitMask(
"DETECTED_NEGATIVE")
728 results.negative =
None
729 elif self.config.thresholdPolarity ==
"negative":
730 if self.config.reEstimateBackground:
731 mask &= ~mask.getPlaneBitMask(
"DETECTED")
732 results.positive =
None
735 def detectFootprints(self, exposure, doSmooth=True, sigma=None, clearMask=True, expId=None,
736 background=None, backgroundToPhotometricRatio=None):
737 """Detect footprints on an exposure.
741 exposure : `lsst.afw.image.Exposure`
742 Exposure to process; DETECTED{,_NEGATIVE} mask plane will be
744 doSmooth : `bool`, optional
745 If True, smooth the image before detection using a Gaussian
746 of width ``sigma``, or the measured PSF width of ``exposure``.
747 Set to False when running on e.g. a pre-convolved image, or a mask
749 sigma : `float`, optional
750 Gaussian Sigma of PSF (pixels); used for smoothing and to grow
751 detections; if `None` then measure the sigma of the PSF of the
753 clearMask : `bool`, optional
754 Clear both DETECTED and DETECTED_NEGATIVE planes before running
756 expId : `dict`, optional
757 Exposure identifier; unused by this implementation, but used for
758 RNG seed by subclasses.
759 background : `lsst.afw.math.BackgroundList`, optional
760 Background that was already subtracted from the exposure; will be
761 modified in-place if ``reEstimateBackground=True``.
762 backgroundToPhotometricRatio : `lsst.afw.image.Image`, optional
763 Image to convert photometric-flattened image to
764 background-flattened image if ``reEstimateBackground=True`` and
765 exposure has been photometric-flattened.
769 results : `lsst.pipe.base.Struct`
770 A `~lsst.pipe.base.Struct` containing:
773 Positive polarity footprints.
774 (`lsst.afw.detection.FootprintSet` or `None`)
776 Negative polarity footprints.
777 (`lsst.afw.detection.FootprintSet` or `None`)
779 Number of footprints in positive or 0 if detection polarity was
782 Number of footprints in negative or 0 if detection polarity was
785 Re-estimated background. `None` or the input ``background``
786 if ``reEstimateBackground==False``.
787 (`lsst.afw.math.BackgroundList`)
789 Multiplication factor applied to the configured detection
792 maskedImage = exposure.maskedImage
797 psf = self.
getPsf(exposure, sigma=sigma)
799 convolveResults = self.
convolveImage(maskedImage, psf, doSmooth=doSmooth)
800 middle = convolveResults.middle
801 sigma = convolveResults.sigma
807 if self.config.doTempLocalBackground:
814 results.positive = self.
setPeakSignificance(middle, results.positive, results.positiveThreshold)
815 results.negative = self.
setPeakSignificance(middle, results.negative, results.negativeThreshold,
818 if self.config.reEstimateBackground:
822 backgroundToPhotometricRatio=backgroundToPhotometricRatio,
827 self.
display(exposure, results, middle)
832 """Set the significance of flagged pixels to zero.
836 middle : `lsst.afw.image.Exposure`
837 Score or maximum likelihood difference image.
838 The image plane will be modified in place.
840 badPixelMask = middle.mask.getPlaneBitMask(self.config.excludeMaskPlanes)
841 badPixels = middle.mask.array & badPixelMask > 0
842 middle.image.array[badPixels] = 0
845 """Set the significance of each detected peak to the pixel value divided
846 by the appropriate standard-deviation for ``config.thresholdType``.
848 Only sets significance for "stdev" and "pixel_stdev" thresholdTypes;
849 we leave it undefined for "value" and "variance" as it does not have a
850 well-defined meaning in those cases.
854 exposure : `lsst.afw.image.Exposure`
855 Exposure that footprints were detected on, likely the convolved,
856 local background-subtracted image.
857 footprints : `lsst.afw.detection.FootprintSet`
858 Footprints detected on the image.
859 threshold : `lsst.afw.detection.Threshold`
860 Threshold used to find footprints.
861 negative : `bool`, optional
862 Are we calculating for negative sources?
864 if footprints
is None or footprints.getFootprints() == []:
866 polarity = -1
if negative
else 1
870 mapper.addMinimalSchema(footprints.getFootprints()[0].peaks.schema)
871 mapper.addOutputField(
"significance", type=float,
872 doc=
"Ratio of peak value to configured standard deviation.")
878 for old, new
in zip(footprints.getFootprints(), newFootprints.getFootprints()):
880 newPeaks.extend(old.peaks, mapper=mapper)
881 new.getPeaks().clear()
882 new.setPeakCatalog(newPeaks)
885 if self.config.thresholdType ==
"pixel_stdev":
886 for footprint
in newFootprints.getFootprints():
887 footprint.updatePeakSignificance(exposure.variance, polarity)
888 elif self.config.thresholdType ==
"stdev":
889 sigma = threshold.getValue() / self.config.thresholdValue
890 for footprint
in newFootprints.getFootprints():
891 footprint.updatePeakSignificance(polarity*sigma)
893 for footprint
in newFootprints.getFootprints():
894 for peak
in footprint.peaks:
895 peak[
"significance"] = 0
900 """Make an afw.detection.Threshold object corresponding to the task's
901 configuration and the statistics of the given image.
905 image : `afw.image.MaskedImage`
906 Image to measure noise statistics from if needed.
907 thresholdParity: `str`
908 One of "positive" or "negative", to set the kind of fluctuations
909 the Threshold will detect.
911 Factor by which to multiply the configured detection threshold.
912 This is useful for tweaking the detection threshold slightly.
916 threshold : `lsst.afw.detection.Threshold`
919 parity =
False if thresholdParity ==
"negative" else True
920 thresholdValue = self.config.thresholdValue
921 thresholdType = self.config.thresholdType
922 if self.config.thresholdType ==
'stdev':
923 bad = image.getMask().getPlaneBitMask(self.config.statsMask)
925 sctrl.setAndMask(bad)
927 thresholdValue *= stats.getValue(afwMath.STDEVCLIP)
928 thresholdType =
'value'
931 threshold.setIncludeMultiplier(self.config.includeThresholdMultiplier)
932 self.log.debug(
"Detection threshold: %s", threshold)
936 """Update the Peaks in a FootprintSet by detecting new Footprints and
937 Peaks in an image and using the new Peaks instead of the old ones.
941 fpSet : `afw.detection.FootprintSet`
942 Set of Footprints whose Peaks should be updated.
943 image : `afw.image.MaskedImage`
944 Image to detect new Footprints and Peak in.
945 threshold : `afw.detection.Threshold`
946 Threshold object for detection.
948 Input Footprints with fewer Peaks than self.config.nPeaksMaxSimple
949 are not modified, and if no new Peaks are detected in an input
950 Footprint, the brightest original Peak in that Footprint is kept.
952 for footprint
in fpSet.getFootprints():
953 oldPeaks = footprint.getPeaks()
954 if len(oldPeaks) <= self.config.nPeaksMaxSimple:
959 sub = image.Factory(image, footprint.getBBox())
964 self.config.minPixels
967 for fpForPeaks
in fpSetForPeaks.getFootprints():
968 for peak
in fpForPeaks.getPeaks():
969 if footprint.contains(peak.getI()):
970 newPeaks.append(peak)
971 if len(newPeaks) > 0:
973 oldPeaks.extend(newPeaks)
979 """Set the edgeBitmask bits for all of maskedImage outside goodBBox
983 maskedImage : `lsst.afw.image.MaskedImage`
984 Image on which to set edge bits in the mask.
985 goodBBox : `lsst.geom.Box2I`
986 Bounding box of good pixels, in ``LOCAL`` coordinates.
987 edgeBitmask : `lsst.afw.image.MaskPixel`
988 Bit mask to OR with the existing mask bits in the region
989 outside ``goodBBox``.
991 msk = maskedImage.getMask()
993 mx0, my0 = maskedImage.getXY0()
994 for x0, y0, w, h
in ([0, 0,
995 msk.getWidth(), goodBBox.getBeginY() - my0],
996 [0, goodBBox.getEndY() - my0, msk.getWidth(),
997 maskedImage.getHeight() - (goodBBox.getEndY() - my0)],
999 goodBBox.getBeginX() - mx0, msk.getHeight()],
1000 [goodBBox.getEndX() - mx0, 0,
1001 maskedImage.getWidth() - (goodBBox.getEndX() - mx0), msk.getHeight()],
1005 edgeMask |= edgeBitmask
1009 """Context manager for removing wide (large-scale) background
1011 Removing a wide (large-scale) background helps to suppress the
1012 detection of large footprints that may overwhelm the deblender.
1013 It does, however, set a limit on the maximum scale of objects.
1015 The background that we remove will be restored upon exit from
1016 the context manager.
1020 exposure : `lsst.afw.image.Exposure`
1021 Exposure on which to remove large-scale background.
1025 context : context manager
1026 Context manager that will ensure the temporary wide background
1029 doTempWideBackground = self.config.doTempWideBackground
1030 if doTempWideBackground:
1031 self.log.info(
"Applying temporary wide background subtraction")
1032 original = exposure.maskedImage.image.array[:].copy()
1033 self.tempWideBackground.run(exposure).background
1036 image = exposure.maskedImage.image
1037 mask = exposure.maskedImage.mask
1038 noData = mask.array & mask.getPlaneBitMask(
"NO_DATA") > 0
1039 isGood = mask.array & mask.getPlaneBitMask(self.config.statsMask) == 0
1040 image.array[noData] = np.median(image.array[~noData & isGood])
1044 if doTempWideBackground:
1045 exposure.maskedImage.image.array[:] = original
1048def addExposures(exposureList):
1049 """Add a set of exposures together.
1053 exposureList : `list` of `lsst.afw.image.Exposure`
1054 Sequence of exposures to add.
1058 addedExposure : `lsst.afw.image.Exposure`
1059 An exposure of the same size as each exposure in ``exposureList``,
1060 with the metadata from ``exposureList[0]`` and a masked image equal
1061 to the sum of all the exposure's masked images.
1063 exposure0 = exposureList[0]
1064 image0 = exposure0.getMaskedImage()
1066 addedImage = image0.Factory(image0,
True)
1067 addedImage.setXY0(image0.getXY0())
1069 for exposure
in exposureList[1:]:
1070 image = exposure.getMaskedImage()
1073 addedExposure = exposure0.Factory(addedImage, exposure0.getWcs())
1074 return addedExposure
1048def addExposures(exposureList):
…
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)
finalizeFootprints(self, mask, results, sigma, factor=1.0, factorNeg=None)
removeBadPixels(self, middle)
__init__(self, schema=None, **kwds)
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)