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'],
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 expects the image to have been background subtracted first.
174 Running detection on images with a non-zero-centered background may result
175 in a single source detected on the entire image containing thousands of
176 peaks, or other pathological outputs.
180 schema : `lsst.afw.table.Schema`
181 Schema object used to create the output `lsst.afw.table.SourceCatalog`
183 Keyword arguments passed to `lsst.pipe.base.Task.__init__`
185 If schema is not None and configured for 'both' detections,
186 a 'flags.negative' field will be added to label detections made with a
191 This task convolves the image with a Gaussian approximation to the PSF,
192 matched to the sigma of the input exposure, because this is separable and
193 fast. The PSF would have to be very non-Gaussian or non-circular for this
194 approximation to have a significant impact on the signal-to-noise of the
197 ConfigClass = SourceDetectionConfig
198 _DefaultName =
"sourceDetection"
201 pipeBase.Task.__init__(self, **kwds)
202 if schema
is not None and self.config.thresholdPolarity ==
"both":
204 "flags_negative", type=
"Flag",
205 doc=
"set if source was detected as significantly negative"
208 if self.config.thresholdPolarity ==
"both":
209 self.log.warning(
"Detection polarity set to 'both', but no flag will be "
210 "set to distinguish between positive and negative detections")
212 if self.config.reEstimateBackground:
213 self.makeSubtask(
"background")
214 if self.config.doTempLocalBackground:
215 self.makeSubtask(
"tempLocalBackground")
216 if self.config.doTempWideBackground:
217 self.makeSubtask(
"tempWideBackground")
220 def run(self, table, exposure, doSmooth=True, sigma=None, clearMask=True, expId=None,
222 r"""Detect sources and return catalog(s) of detections.
226 table : `lsst.afw.table.SourceTable`
227 Table object that will be used to create the SourceCatalog.
228 exposure : `lsst.afw.image.Exposure`
229 Exposure to process; DETECTED mask plane will be set in-place.
230 doSmooth : `bool`, optional
231 If True, smooth the image before detection using a Gaussian of width
232 ``sigma``, or the measured PSF width. Set to False when running on
233 e.g. a pre-convolved image, or a mask plane.
234 sigma : `float`, optional
235 Sigma of PSF (pixels); used for smoothing and to grow detections;
236 if None then measure the sigma of the PSF of the exposure
237 clearMask : `bool`, optional
238 Clear DETECTED{,_NEGATIVE} planes before running detection.
239 expId : `int`, optional
240 Exposure identifier; unused by this implementation, but used for
241 RNG seed by subclasses.
242 background : `lsst.afw.math.BackgroundList`, optional
243 Background that was already subtracted from the exposure; will be
244 modified in-place if ``reEstimateBackground=True``.
248 result : `lsst.pipe.base.Struct`
249 The `~lsst.pipe.base.Struct` contains:
252 Detected sources on the exposure.
253 (`lsst.afw.table.SourceCatalog`)
255 Positive polarity footprints.
256 (`lsst.afw.detection.FootprintSet` or `None`)
258 Negative polarity footprints.
259 (`lsst.afw.detection.FootprintSet` or `None`)
261 Number of footprints in positive or 0 if detection polarity was
264 Number of footprints in negative or 0 if detection polarity was
267 Re-estimated background. `None` if
268 ``reEstimateBackground==False``.
269 (`lsst.afw.math.BackgroundList`)
271 Multiplication factor applied to the configured detection
277 Raised if flags.negative is needed, but isn't in table's schema.
278 lsst.pipe.base.TaskError
279 Raised if sigma=None, doSmooth=True and the exposure has no PSF.
283 If you want to avoid dealing with Sources and Tables, you can use
284 `detectFootprints()` to just get the
285 `~lsst.afw.detection.FootprintSet`\s.
288 raise ValueError(
"Table has incorrect Schema")
289 results = self.
detectFootprints(exposure=exposure, doSmooth=doSmooth, sigma=sigma,
290 clearMask=clearMask, expId=expId, background=background)
292 sources.reserve(results.numPos + results.numNeg)
294 results.negative.makeSources(sources)
296 for record
in sources:
299 results.positive.makeSources(sources)
300 results.sources = sources
303 def display(self, exposure, results, convolvedImage=None):
304 """Display detections if so configured
306 Displays the ``exposure`` in frame 0, overlays the detection peaks.
308 Requires that ``lsstDebug`` has been set up correctly, so that
309 ``lsstDebug.Info("lsst.meas.algorithms.detection")`` evaluates `True`.
311 If the ``convolvedImage`` is non-`None` and
312 ``lsstDebug.Info("lsst.meas.algorithms.detection") > 1``, the
313 ``convolvedImage`` will be displayed in frame 1.
317 exposure : `lsst.afw.image.Exposure`
318 Exposure to display, on which will be plotted the detections.
319 results : `lsst.pipe.base.Struct`
320 Results of the 'detectFootprints' method, containing positive and
321 negative footprints (which contain the peak positions that we will
322 plot). This is a `Struct` with ``positive`` and ``negative``
323 elements that are of type `lsst.afw.detection.FootprintSet`.
324 convolvedImage : `lsst.afw.image.Image`, optional
325 Convolved image used for thresholding.
338 afwDisplay.setDefaultMaskTransparency(75)
340 disp0 = afwDisplay.Display(frame=0)
341 disp0.mtv(exposure, title=
"detection")
343 def plotPeaks(fps, ctype):
346 with disp0.Buffering():
347 for fp
in fps.getFootprints():
348 for pp
in fp.getPeaks():
349 disp0.dot(
"+", pp.getFx(), pp.getFy(), ctype=ctype)
350 plotPeaks(results.positive,
"yellow")
351 plotPeaks(results.negative,
"red")
353 if convolvedImage
and display > 1:
354 disp1 = afwDisplay.Display(frame=1)
355 disp1.mtv(convolvedImage, title=
"PSF smoothed")
357 disp2 = afwDisplay.Display(frame=2)
358 disp2.mtv(afwImage.ImageF(np.sqrt(exposure.variance.array)), title=
"stddev")
361 """Apply a temporary local background subtraction
363 This temporary local background serves to suppress noise fluctuations
364 in the wings of bright objects.
366 Peaks in the footprints will be updated.
370 exposure : `lsst.afw.image.Exposure`
371 Exposure for which to fit local background.
372 middle : `lsst.afw.image.MaskedImage`
373 Convolved image on which detection will be performed
374 (typically smaller than ``exposure`` because the
375 half-kernel has been removed around the edges).
376 results : `lsst.pipe.base.Struct`
377 Results of the 'detectFootprints' method, containing positive and
378 negative footprints (which contain the peak positions that we will
379 plot). This is a `Struct` with ``positive`` and ``negative``
380 elements that are of type `lsst.afw.detection.FootprintSet`.
385 bg = self.tempLocalBackground.fitBackground(exposure.getMaskedImage())
386 bgImage = bg.getImageF(self.tempLocalBackground.config.algorithm,
387 self.tempLocalBackground.config.undersampleStyle)
388 middle -= bgImage.Factory(bgImage, middle.getBBox())
389 if self.config.thresholdPolarity !=
"negative":
390 results.positiveThreshold = self.
makeThreshold(middle,
"positive")
391 self.
updatePeaks(results.positive, middle, results.positiveThreshold)
392 if self.config.thresholdPolarity !=
"positive":
393 results.negativeThreshold = self.
makeThreshold(middle,
"negative")
394 self.
updatePeaks(results.negative, middle, results.negativeThreshold)
397 """Clear the DETECTED and DETECTED_NEGATIVE mask planes.
399 Removes any previous detection mask in preparation for a new
404 mask : `lsst.afw.image.Mask`
407 mask &= ~(mask.getPlaneBitMask(
"DETECTED") | mask.getPlaneBitMask(
"DETECTED_NEGATIVE"))
410 """Calculate the size of the smoothing kernel.
412 Uses the ``nSigmaForKernel`` configuration parameter. Note
413 that that is the full width of the kernel bounding box
414 (so a value of 7 means 3.5 sigma on either side of center).
415 The value will be rounded up to the nearest odd integer.
420 Gaussian sigma of smoothing kernel.
425 Size of the smoothing kernel.
427 return (int(sigma * self.config.nSigmaForKernel + 0.5)//2)*2 + 1
430 """Create a single Gaussian PSF for an exposure.
432 If ``sigma`` is provided, we make a `~lsst.afw.detection.GaussianPsf`
433 with that, otherwise use the sigma from the psf of the ``exposure`` to
434 make the `~lsst.afw.detection.GaussianPsf`.
438 exposure : `lsst.afw.image.Exposure`
439 Exposure from which to retrieve the PSF.
440 sigma : `float`, optional
441 Gaussian sigma to use if provided.
445 psf : `lsst.afw.detection.GaussianPsf`
446 PSF to use for detection.
451 Raised if ``sigma`` is not provided and ``exposure`` does not
452 contain a ``Psf`` object.
455 psf = exposure.getPsf()
457 raise RuntimeError(
"Unable to determine PSF to use for detection: no sigma provided")
458 sigma = psf.computeShape(psf.getAveragePosition()).getDeterminantRadius()
464 """Convolve the image with the PSF.
466 We convolve the image with a Gaussian approximation to the PSF,
467 because this is separable and therefore fast. It's technically a
468 correlation rather than a convolution, but since we use a symmetric
469 Gaussian there's no difference.
471 The convolution can be disabled with ``doSmooth=False``. If we do
472 convolve, we mask the edges as ``EDGE`` and return the convolved image
473 with the edges removed. This is because we can't convolve the edges
474 because the kernel would extend off the image.
478 maskedImage : `lsst.afw.image.MaskedImage`
480 psf : `lsst.afw.detection.Psf`
481 PSF to convolve with (actually with a Gaussian approximation
484 Actually do the convolution? Set to False when running on
485 e.g. a pre-convolved image, or a mask plane.
489 results : `lsst.pipe.base.Struct`
490 The `~lsst.pipe.base.Struct` contains:
493 Convolved image, without the edges. (`lsst.afw.image.MaskedImage`)
495 Gaussian sigma used for the convolution. (`float`)
497 self.metadata[
"doSmooth"] = doSmooth
498 sigma = psf.computeShape(psf.getAveragePosition()).getDeterminantRadius()
499 self.metadata[
"sigma"] = sigma
502 middle = maskedImage.Factory(maskedImage, deep=
True)
503 return pipeBase.Struct(middle=middle, sigma=sigma)
508 self.metadata[
"smoothingKernelWidth"] = kWidth
509 gaussFunc = afwMath.GaussianFunction1D(sigma)
512 convolvedImage = maskedImage.Factory(maskedImage.getBBox())
517 goodBBox = gaussKernel.shrinkBBox(convolvedImage.getBBox())
518 middle = convolvedImage.Factory(convolvedImage, goodBBox, afwImage.PARENT,
False)
521 self.
setEdgeBits(maskedImage, goodBBox, maskedImage.getMask().getPlaneBitMask(
"EDGE"))
523 return pipeBase.Struct(middle=middle, sigma=sigma)
526 r"""Apply thresholds to the convolved image
528 Identifies `~lsst.afw.detection.Footprint`\s, both positive and negative.
529 The threshold can be modified by the provided multiplication
534 middle : `lsst.afw.image.MaskedImage`
535 Convolved image to threshold.
536 bbox : `lsst.geom.Box2I`
537 Bounding box of unconvolved image.
539 Multiplier for the configured threshold.
540 factorNeg : `float` or `None`
541 Multiplier for the configured threshold for negative detection polarity.
542 If `None`, will be set equal to ``factor`` (i.e. equal to the factor used
543 for positive detection polarity).
547 results : `lsst.pipe.base.Struct`
548 The `~lsst.pipe.base.Struct` contains:
551 Positive detection footprints, if configured.
552 (`lsst.afw.detection.FootprintSet` or `None`)
554 Negative detection footprints, if configured.
555 (`lsst.afw.detection.FootprintSet` or `None`)
557 Multiplier for the configured threshold.
560 Multiplier for the configured threshold for negative detection polarity.
563 if factorNeg
is None:
565 self.log.info(
"Setting factor for negative detections equal to that for positive "
566 "detections: %f", factor)
567 results = pipeBase.Struct(positive=
None, negative=
None, factor=factor, factorNeg=factorNeg,
568 positiveThreshold=
None, negativeThreshold=
None)
570 if self.config.reEstimateBackground
or self.config.thresholdPolarity !=
"negative":
571 results.positiveThreshold = self.
makeThreshold(middle,
"positive", factor=factor)
574 results.positiveThreshold,
576 self.config.minPixels
578 results.positive.setRegion(bbox)
579 if self.config.reEstimateBackground
or self.config.thresholdPolarity !=
"positive":
580 results.negativeThreshold = self.
makeThreshold(middle,
"negative", factor=factorNeg)
583 results.negativeThreshold,
585 self.config.minPixels
587 results.negative.setRegion(bbox)
592 """Finalize the detected footprints.
594 Grow the footprints, set the ``DETECTED`` and ``DETECTED_NEGATIVE``
595 mask planes, and log the results.
597 ``numPos`` (number of positive footprints), ``numPosPeaks`` (number
598 of positive peaks), ``numNeg`` (number of negative footprints),
599 ``numNegPeaks`` (number of negative peaks) entries are added to the
604 mask : `lsst.afw.image.Mask`
605 Mask image on which to flag detected pixels.
606 results : `lsst.pipe.base.Struct`
607 Struct of detection results, including ``positive`` and
608 ``negative`` entries; modified.
610 Gaussian sigma of PSF.
612 Multiplier for the configured threshold. Note that this is only
613 used here for logging purposes.
614 factorNeg : `float` or `None`
615 Multiplier used for the negative detection polarity threshold.
616 If `None`, a factor equal to ``factor`` (i.e. equal to the one used
617 for positive detection polarity) is assumed. Note that this is only
618 used here for logging purposes.
620 factorNeg = factor
if factorNeg
is None else factorNeg
621 for polarity, maskName
in ((
"positive",
"DETECTED"), (
"negative",
"DETECTED_NEGATIVE")):
622 fpSet = getattr(results, polarity)
625 if self.config.nSigmaToGrow > 0:
626 nGrow = int((self.config.nSigmaToGrow * sigma) + 0.5)
627 self.metadata[
"nGrow"] = nGrow
628 if self.config.combinedGrow:
631 stencil = (afwGeom.Stencil.CIRCLE
if self.config.isotropicGrow
else
632 afwGeom.Stencil.MANHATTAN)
634 fp.dilate(nGrow, stencil)
635 fpSet.setMask(mask, maskName)
636 if not self.config.returnOriginalFootprints:
637 setattr(results, polarity, fpSet)
640 results.numPosPeaks = 0
642 results.numNegPeaks = 0
646 if results.positive
is not None:
647 results.numPos = len(results.positive.getFootprints())
648 results.numPosPeaks = sum(len(fp.getPeaks())
for fp
in results.positive.getFootprints())
649 positive =
" %d positive peaks in %d footprints" % (results.numPosPeaks, results.numPos)
650 if results.negative
is not None:
651 results.numNeg = len(results.negative.getFootprints())
652 results.numNegPeaks = sum(len(fp.getPeaks())
for fp
in results.negative.getFootprints())
653 negative =
" %d negative peaks in %d footprints" % (results.numNegPeaks, results.numNeg)
655 self.log.info(
"Detected%s%s%s to %g +ve and %g -ve %s",
656 positive,
" and" if positive
and negative
else "", negative,
657 self.config.thresholdValue*self.config.includeThresholdMultiplier*factor,
658 self.config.thresholdValue*self.config.includeThresholdMultiplier*factorNeg,
659 "DN" if self.config.thresholdType ==
"value" else "sigma")
662 """Estimate the background after detection
666 maskedImage : `lsst.afw.image.MaskedImage`
667 Image on which to estimate the background.
668 backgrounds : `lsst.afw.math.BackgroundList`
669 List of backgrounds; modified.
673 bg : `lsst.afw.math.backgroundMI`
674 Empirical background model.
676 bg = self.background.fitBackground(maskedImage)
677 if self.config.adjustBackground:
678 self.log.warning(
"Fiddling the background by %g", self.config.adjustBackground)
679 bg += self.config.adjustBackground
680 self.log.info(
"Resubtracting the background after object detection")
681 maskedImage -= bg.getImageF(self.background.config.algorithm,
682 self.background.config.undersampleStyle)
684 actrl = bg.getBackgroundControl().getApproximateControl()
686 bg.getAsUsedUndersampleStyle(), actrl.getStyle(), actrl.getOrderX(),
687 actrl.getOrderY(), actrl.getWeighting()))
691 """Clear unwanted results from the Struct of results
693 If we specifically want only positive or only negative detections,
694 drop the ones we don't want, and its associated mask plane.
698 mask : `lsst.afw.image.Mask`
700 results : `lsst.pipe.base.Struct`
701 Detection results, with ``positive`` and ``negative`` elements;
704 if self.config.thresholdPolarity ==
"positive":
705 if self.config.reEstimateBackground:
706 mask &= ~mask.getPlaneBitMask(
"DETECTED_NEGATIVE")
707 results.negative =
None
708 elif self.config.thresholdPolarity ==
"negative":
709 if self.config.reEstimateBackground:
710 mask &= ~mask.getPlaneBitMask(
"DETECTED")
711 results.positive =
None
714 def detectFootprints(self, exposure, doSmooth=True, sigma=None, clearMask=True, expId=None,
716 """Detect footprints on an exposure.
720 exposure : `lsst.afw.image.Exposure`
721 Exposure to process; DETECTED{,_NEGATIVE} mask plane will be
723 doSmooth : `bool`, optional
724 If True, smooth the image before detection using a Gaussian
725 of width ``sigma``, or the measured PSF width of ``exposure``.
726 Set to False when running on e.g. a pre-convolved image, or a mask
728 sigma : `float`, optional
729 Gaussian Sigma of PSF (pixels); used for smoothing and to grow
730 detections; if `None` then measure the sigma of the PSF of the
732 clearMask : `bool`, optional
733 Clear both DETECTED and DETECTED_NEGATIVE planes before running
735 expId : `dict`, optional
736 Exposure identifier; unused by this implementation, but used for
737 RNG seed by subclasses.
738 background : `lsst.afw.math.BackgroundList`, optional
739 Background that was already subtracted from the exposure; will be
740 modified in-place if ``reEstimateBackground=True``.
744 results : `lsst.pipe.base.Struct`
745 A `~lsst.pipe.base.Struct` containing:
748 Positive polarity footprints.
749 (`lsst.afw.detection.FootprintSet` or `None`)
751 Negative polarity footprints.
752 (`lsst.afw.detection.FootprintSet` or `None`)
754 Number of footprints in positive or 0 if detection polarity was
757 Number of footprints in negative or 0 if detection polarity was
760 Re-estimated background. `None` or the input ``background``
761 if ``reEstimateBackground==False``.
762 (`lsst.afw.math.BackgroundList`)
764 Multiplication factor applied to the configured detection
767 maskedImage = exposure.maskedImage
772 psf = self.
getPsf(exposure, sigma=sigma)
774 convolveResults = self.
convolveImage(maskedImage, psf, doSmooth=doSmooth)
775 middle = convolveResults.middle
776 sigma = convolveResults.sigma
782 if self.config.doTempLocalBackground:
789 results.positive = self.
setPeakSignificance(middle, results.positive, results.positiveThreshold)
790 results.negative = self.
setPeakSignificance(middle, results.negative, results.negativeThreshold,
793 if self.config.reEstimateBackground:
798 self.
display(exposure, results, middle)
803 """Set the significance of flagged pixels to zero.
807 middle : `lsst.afw.image.Exposure`
808 Score or maximum likelihood difference image.
809 The image plane will be modified in place.
811 badPixelMask = middle.mask.getPlaneBitMask(self.config.excludeMaskPlanes)
812 badPixels = middle.mask.array & badPixelMask > 0
813 middle.image.array[badPixels] = 0
816 """Set the significance of each detected peak to the pixel value divided
817 by the appropriate standard-deviation for ``config.thresholdType``.
819 Only sets significance for "stdev" and "pixel_stdev" thresholdTypes;
820 we leave it undefined for "value" and "variance" as it does not have a
821 well-defined meaning in those cases.
825 exposure : `lsst.afw.image.Exposure`
826 Exposure that footprints were detected on, likely the convolved,
827 local background-subtracted image.
828 footprints : `lsst.afw.detection.FootprintSet`
829 Footprints detected on the image.
830 threshold : `lsst.afw.detection.Threshold`
831 Threshold used to find footprints.
832 negative : `bool`, optional
833 Are we calculating for negative sources?
835 if footprints
is None or footprints.getFootprints() == []:
837 polarity = -1
if negative
else 1
841 mapper.addMinimalSchema(footprints.getFootprints()[0].peaks.schema)
842 mapper.addOutputField(
"significance", type=float,
843 doc=
"Ratio of peak value to configured standard deviation.")
849 for old, new
in zip(footprints.getFootprints(), newFootprints.getFootprints()):
851 newPeaks.extend(old.peaks, mapper=mapper)
852 new.getPeaks().clear()
853 new.setPeakCatalog(newPeaks)
856 if self.config.thresholdType ==
"pixel_stdev":
857 for footprint
in newFootprints.getFootprints():
858 footprint.updatePeakSignificance(exposure.variance, polarity)
859 elif self.config.thresholdType ==
"stdev":
860 sigma = threshold.getValue() / self.config.thresholdValue
861 for footprint
in newFootprints.getFootprints():
862 footprint.updatePeakSignificance(polarity*sigma)
864 for footprint
in newFootprints.getFootprints():
865 for peak
in footprint.peaks:
866 peak[
"significance"] = 0
871 """Make an afw.detection.Threshold object corresponding to the task's
872 configuration and the statistics of the given image.
876 image : `afw.image.MaskedImage`
877 Image to measure noise statistics from if needed.
878 thresholdParity: `str`
879 One of "positive" or "negative", to set the kind of fluctuations
880 the Threshold will detect.
882 Factor by which to multiply the configured detection threshold.
883 This is useful for tweaking the detection threshold slightly.
887 threshold : `lsst.afw.detection.Threshold`
890 parity =
False if thresholdParity ==
"negative" else True
891 thresholdValue = self.config.thresholdValue
892 thresholdType = self.config.thresholdType
893 if self.config.thresholdType ==
'stdev':
894 bad = image.getMask().getPlaneBitMask(self.config.statsMask)
896 sctrl.setAndMask(bad)
898 thresholdValue *= stats.getValue(afwMath.STDEVCLIP)
899 thresholdType =
'value'
902 threshold.setIncludeMultiplier(self.config.includeThresholdMultiplier)
903 self.log.debug(
"Detection threshold: %s", threshold)
907 """Update the Peaks in a FootprintSet by detecting new Footprints and
908 Peaks in an image and using the new Peaks instead of the old ones.
912 fpSet : `afw.detection.FootprintSet`
913 Set of Footprints whose Peaks should be updated.
914 image : `afw.image.MaskedImage`
915 Image to detect new Footprints and Peak in.
916 threshold : `afw.detection.Threshold`
917 Threshold object for detection.
919 Input Footprints with fewer Peaks than self.config.nPeaksMaxSimple
920 are not modified, and if no new Peaks are detected in an input
921 Footprint, the brightest original Peak in that Footprint is kept.
923 for footprint
in fpSet.getFootprints():
924 oldPeaks = footprint.getPeaks()
925 if len(oldPeaks) <= self.config.nPeaksMaxSimple:
930 sub = image.Factory(image, footprint.getBBox())
935 self.config.minPixels
938 for fpForPeaks
in fpSetForPeaks.getFootprints():
939 for peak
in fpForPeaks.getPeaks():
940 if footprint.contains(peak.getI()):
941 newPeaks.append(peak)
942 if len(newPeaks) > 0:
944 oldPeaks.extend(newPeaks)
950 """Set the edgeBitmask bits for all of maskedImage outside goodBBox
954 maskedImage : `lsst.afw.image.MaskedImage`
955 Image on which to set edge bits in the mask.
956 goodBBox : `lsst.geom.Box2I`
957 Bounding box of good pixels, in ``LOCAL`` coordinates.
958 edgeBitmask : `lsst.afw.image.MaskPixel`
959 Bit mask to OR with the existing mask bits in the region
960 outside ``goodBBox``.
962 msk = maskedImage.getMask()
964 mx0, my0 = maskedImage.getXY0()
965 for x0, y0, w, h
in ([0, 0,
966 msk.getWidth(), goodBBox.getBeginY() - my0],
967 [0, goodBBox.getEndY() - my0, msk.getWidth(),
968 maskedImage.getHeight() - (goodBBox.getEndY() - my0)],
970 goodBBox.getBeginX() - mx0, msk.getHeight()],
971 [goodBBox.getEndX() - mx0, 0,
972 maskedImage.getWidth() - (goodBBox.getEndX() - mx0), msk.getHeight()],
976 edgeMask |= edgeBitmask
980 """Context manager for removing wide (large-scale) background
982 Removing a wide (large-scale) background helps to suppress the
983 detection of large footprints that may overwhelm the deblender.
984 It does, however, set a limit on the maximum scale of objects.
986 The background that we remove will be restored upon exit from
991 exposure : `lsst.afw.image.Exposure`
992 Exposure on which to remove large-scale background.
996 context : context manager
997 Context manager that will ensure the temporary wide background
1000 doTempWideBackground = self.config.doTempWideBackground
1001 if doTempWideBackground:
1002 self.log.info(
"Applying temporary wide background subtraction")
1003 original = exposure.maskedImage.image.array[:].copy()
1004 self.tempWideBackground.run(exposure).background
1007 image = exposure.maskedImage.image
1008 mask = exposure.maskedImage.mask
1009 noData = mask.array & mask.getPlaneBitMask(
"NO_DATA") > 0
1010 isGood = mask.array & mask.getPlaneBitMask(self.config.statsMask) == 0
1011 image.array[noData] = np.median(image.array[~noData & isGood])
1015 if doTempWideBackground:
1016 exposure.maskedImage.image.array[:] = original
1020 """Add a set of exposures together.
1024 exposureList : `list` of `lsst.afw.image.Exposure`
1025 Sequence of exposures to add.
1029 addedExposure : `lsst.afw.image.Exposure`
1030 An exposure of the same size as each exposure in ``exposureList``,
1031 with the metadata from ``exposureList[0]`` and a masked image equal
1032 to the sum of all the exposure's masked images.
1034 exposure0 = exposureList[0]
1035 image0 = exposure0.getMaskedImage()
1037 addedImage = image0.Factory(image0,
True)
1038 addedImage.setXY0(image0.getXY0())
1040 for exposure
in exposureList[1:]:
1041 image = exposure.getMaskedImage()
1044 addedExposure = exposure0.Factory(addedImage, exposure0.getWcs())
1045 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)
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)
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.
addExposures(exposureList)