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=
"Pixels should be grown as isotropically as possible (slower)",
52 dtype=bool, optional=
False, default=
False,
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 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=
"Include threshold relative to thresholdValue",
72 dtype=float, default=1.0, min=0.0,
74 thresholdType = pexConfig.ChoiceField(
75 doc=
"specifies the desired flavor of Threshold",
76 dtype=str, optional=
False, default=
"stdev",
78 "variance":
"threshold applied to image variance",
79 "stdev":
"threshold applied to image std deviation",
80 "value":
"threshold applied to image value",
81 "pixel_stdev":
"threshold applied to per-pixel std deviation",
84 thresholdPolarity = pexConfig.ChoiceField(
85 doc=
"specifies whether to detect positive, or negative sources, or both",
86 dtype=str, optional=
False, default=
"positive",
88 "positive":
"detect only positive sources",
89 "negative":
"detect only negative sources",
90 "both":
"detect both positive and negative sources",
93 adjustBackground = pexConfig.Field(
95 doc=
"Fiddle factor to add to the background; debugging only",
98 reEstimateBackground = pexConfig.Field(
100 doc=
"Estimate the background again after final source detection?",
101 default=
True, optional=
False,
103 background = pexConfig.ConfigurableField(
104 doc=
"Background re-estimation; ignored if reEstimateBackground false",
105 target=SubtractBackgroundTask,
107 tempLocalBackground = pexConfig.ConfigurableField(
108 doc=(
"A local (small-scale), temporary background estimation step run between "
109 "detecting above-threshold regions and detecting the peaks within "
110 "them; used to avoid detecting spuerious peaks in the wings."),
111 target=SubtractBackgroundTask,
113 doTempLocalBackground = pexConfig.Field(
115 doc=
"Enable temporary local background subtraction? (see tempLocalBackground)",
118 tempWideBackground = pexConfig.ConfigurableField(
119 doc=(
"A wide (large-scale) background estimation and removal before footprint and peak detection. "
120 "It is added back into the image after detection. The purpose is to suppress very large "
121 "footprints (e.g., from large artifacts) that the deblender may choke on."),
122 target=SubtractBackgroundTask,
124 doTempWideBackground = pexConfig.Field(
126 doc=
"Do temporary wide (large-scale) background subtraction before footprint detection?",
129 nPeaksMaxSimple = pexConfig.Field(
131 doc=(
"The maximum number of peaks in a Footprint before trying to "
132 "replace its peaks using the temporary local background"),
135 nSigmaForKernel = pexConfig.Field(
137 doc=(
"Multiple of PSF RMS size to use for convolution kernel bounding box size; "
138 "note that this is not a half-size. The size will be rounded up to the nearest odd integer"),
141 statsMask = pexConfig.ListField(
143 doc=
"Mask planes to ignore when calculating statistics of image (for thresholdType=stdev)",
144 default=[
'BAD',
'SAT',
'EDGE',
'NO_DATA'],
157 for maskPlane
in (
"DETECTED",
"DETECTED_NEGATIVE"):
163 """Create the detection task. Most arguments are simply passed onto pipe.base.Task.
170 Keyword arguments passed to `lsst.pipe.base.task.Task.__init__`
172 If schema is not None and configured
for 'both' detections,
173 a
'flags.negative' field will be added to label detections made
with a
178 This task can add fields to the schema, so any code calling this task must ensure that
179 these columns are indeed present
in the input match list.
182 ConfigClass = SourceDetectionConfig
183 _DefaultName = "sourceDetection"
186 pipeBase.Task.__init__(self, **kwds)
187 if schema
is not None and self.config.thresholdPolarity ==
"both":
189 "flags_negative", type=
"Flag",
190 doc=
"set if source was detected as significantly negative"
193 if self.config.thresholdPolarity ==
"both":
194 self.log.warning(
"Detection polarity set to 'both', but no flag will be "
195 "set to distinguish between positive and negative detections")
197 if self.config.reEstimateBackground:
198 self.makeSubtask(
"background")
199 if self.config.doTempLocalBackground:
200 self.makeSubtask(
"tempLocalBackground")
201 if self.config.doTempWideBackground:
202 self.makeSubtask(
"tempWideBackground")
205 def run(self, table, exposure, doSmooth=True, sigma=None, clearMask=True, expId=None):
206 r"""Run source detection and create a SourceCatalog of detections.
211 Table object that will be used to create the SourceCatalog.
213 Exposure to process; DETECTED mask plane will be set in-place.
215 If
True, smooth the image before detection using a Gaussian of width
216 ``sigma``,
or the measured PSF width. Set to
False when running on
217 e.g. a pre-convolved image,
or a mask plane.
219 Sigma of PSF (pixels); used
for smoothing
and to grow detections;
220 if None then measure the sigma of the PSF of the exposure
222 Clear DETECTED{,_NEGATIVE} planes before running detection.
224 Exposure identifier; unused by this implementation, but used
for
225 RNG seed by subclasses.
229 result : `lsst.pipe.base.Struct`
230 The `~lsst.pipe.base.Struct` contains:
236 (`lsst.pipe.base.Struct`).
241 Raised
if flags.negative
is needed, but isn
't in table's schema.
242 lsst.pipe.base.TaskError
243 Raised
if sigma=
None, doSmooth=
True and the exposure has no PSF.
247 If you want to avoid dealing
with Sources
and Tables, you can use
252 raise ValueError(
"Table has incorrect Schema")
253 results = self.
detectFootprints(exposure=exposure, doSmooth=doSmooth, sigma=sigma,
254 clearMask=clearMask, expId=expId)
255 sources = afwTable.SourceCatalog(table)
256 sources.reserve(results.numPos + results.numNeg)
258 results.negative.makeSources(sources)
260 for record
in sources:
263 results.positive.makeSources(sources)
264 results.fpSets = results.copy()
265 results.sources = sources
268 def display(self, exposure, results, convolvedImage=None):
269 """Display detections if so configured
271 Displays the ``exposure`` in frame 0, overlays the detection peaks.
273 Requires that ``lsstDebug`` has been set up correctly, so that
274 ``
lsstDebug.Info(
"lsst.meas.algorithms.detection")`` evaluates `
True`.
276 If the ``convolvedImage``
is non-`
None`
and
278 ``convolvedImage`` will be displayed
in frame 1.
283 Exposure to display, on which will be plotted the detections.
284 results : `lsst.pipe.base.Struct`
285 Results of the
'detectFootprints' method, containing positive
and
286 negative footprints (which contain the peak positions that we will
287 plot). This
is a `Struct`
with ``positive``
and ``negative``
290 Convolved image used
for thresholding.
303 afwDisplay.setDefaultMaskTransparency(75)
305 disp0 = afwDisplay.Display(frame=0)
306 disp0.mtv(exposure, title=
"detection")
308 def plotPeaks(fps, ctype):
311 with disp0.Buffering():
312 for fp
in fps.getFootprints():
313 for pp
in fp.getPeaks():
314 disp0.dot(
"+", pp.getFx(), pp.getFy(), ctype=ctype)
315 plotPeaks(results.positive,
"yellow")
316 plotPeaks(results.negative,
"red")
318 if convolvedImage
and display > 1:
319 disp1 = afwDisplay.Display(frame=1)
320 disp1.mtv(convolvedImage, title=
"PSF smoothed")
322 disp2 = afwDisplay.Display(frame=2)
323 disp2.mtv(afwImage.ImageF(np.sqrt(exposure.variance.array)), title=
"stddev")
326 """Apply a temporary local background subtraction
328 This temporary local background serves to suppress noise fluctuations
329 in the wings of bright objects.
331 Peaks
in the footprints will be updated.
336 Exposure
for which to fit local background.
338 Convolved image on which detection will be performed
339 (typically smaller than ``exposure`` because the
340 half-kernel has been removed around the edges).
341 results : `lsst.pipe.base.Struct`
342 Results of the
'detectFootprints' method, containing positive
and
343 negative footprints (which contain the peak positions that we will
344 plot). This
is a `Struct`
with ``positive``
and ``negative``
350 bg = self.tempLocalBackground.fitBackground(exposure.getMaskedImage())
351 bgImage = bg.getImageF(self.tempLocalBackground.config.algorithm,
352 self.tempLocalBackground.config.undersampleStyle)
353 middle -= bgImage.Factory(bgImage, middle.getBBox())
354 if self.config.thresholdPolarity !=
"negative":
355 results.positiveThreshold = self.
makeThreshold(middle,
"positive")
356 self.
updatePeaks(results.positive, middle, results.positiveThreshold)
357 if self.config.thresholdPolarity !=
"positive":
358 results.negativeThreshold = self.
makeThreshold(middle,
"negative")
359 self.
updatePeaks(results.negative, middle, results.negativeThreshold)
362 """Clear the DETECTED and DETECTED_NEGATIVE mask planes
364 Removes any previous detection mask in preparation
for a new
372 mask &= ~(mask.getPlaneBitMask("DETECTED") | mask.getPlaneBitMask(
"DETECTED_NEGATIVE"))
375 """Calculate size of smoothing kernel
377 Uses the ``nSigmaForKernel`` configuration parameter. Note
378 that that is the full width of the kernel bounding box
379 (so a value of 7 means 3.5 sigma on either side of center).
380 The value will be rounded up to the nearest odd integer.
385 Gaussian sigma of smoothing kernel.
390 Size of the smoothing kernel.
392 return (int(sigma * self.config.nSigmaForKernel + 0.5)//2)*2 + 1
395 """Retrieve the PSF for an exposure
397 If ``sigma`` is provided, we make a ``GaussianPsf``
with that,
398 otherwise use the one
from the ``exposure``.
403 Exposure
from which to retrieve the PSF.
404 sigma : `float`, optional
405 Gaussian sigma to use
if provided.
410 PSF to use
for detection.
413 psf = exposure.getPsf()
415 raise RuntimeError(
"Unable to determine PSF to use for detection: no sigma provided")
416 sigma = psf.computeShape(psf.getAveragePosition()).getDeterminantRadius()
418 psf = afwDet.GaussianPsf(size, size, sigma)
422 """Convolve the image with the PSF
424 We convolve the image with a Gaussian approximation to the PSF,
425 because this
is separable
and therefore fast. It
's technically a
426 correlation rather than a convolution, but since we use a symmetric
427 Gaussian there's no difference.
429 The convolution can be disabled with ``doSmooth=
False``. If we do
430 convolve, we mask the edges
as ``EDGE``
and return the convolved image
431 with the edges removed. This
is because we can
't convolve the edges
432 because the kernel would extend off the image.
439 PSF to convolve with (actually
with a Gaussian approximation
442 Actually do the convolution? Set to
False when running on
443 e.g. a pre-convolved image,
or a mask plane.
447 results : `lsst.pipe.base.Struct`
448 The `~lsst.pipe.base.Struct` contains:
453 Gaussian sigma used
for the convolution. (`float`)
455 self.metadata["doSmooth"] = doSmooth
456 sigma = psf.computeShape(psf.getAveragePosition()).getDeterminantRadius()
457 self.metadata[
"sigma"] = sigma
460 middle = maskedImage.Factory(maskedImage, deep=
True)
461 return pipeBase.Struct(middle=middle, sigma=sigma)
466 self.metadata[
"smoothingKernelWidth"] = kWidth
467 gaussFunc = afwMath.GaussianFunction1D(sigma)
468 gaussKernel = afwMath.SeparableKernel(kWidth, kWidth, gaussFunc, gaussFunc)
470 convolvedImage = maskedImage.Factory(maskedImage.getBBox())
472 afwMath.convolve(convolvedImage, maskedImage, gaussKernel, afwMath.ConvolutionControl())
476 goodBBox = gaussKernel.shrinkBBox(convolvedImage.getBBox())
477 middle = convolvedImage.Factory(convolvedImage, goodBBox, afwImage.PARENT,
False)
481 self.
setEdgeBits(maskedImage, goodBBox, maskedImage.getMask().getPlaneBitMask(
"EDGE"))
483 return pipeBase.Struct(middle=middle, sigma=sigma)
486 r"""Apply thresholds to the convolved image
488 Identifies ``Footprint``\ s, both positive and negative.
490 The threshold can be modified by the provided multiplication
496 Convolved image to threshold.
498 Bounding box of unconvolved image.
500 Multiplier
for the configured threshold.
501 factorNeg : `float`
or `
None`
502 Multiplier
for the configured threshold
for negative detection polarity.
503 If `
None`, will be set equal to ``factor`` (i.e. equal to the factor used
504 for positive detection polarity).
508 results : `lsst.pipe.base.Struct`
509 The `~lsst.pipe.base.Struct` contains:
512 Positive detection footprints,
if configured.
515 Negative detection footprints,
if configured.
518 Multiplier
for the configured threshold.
521 Multiplier
for the configured threshold
for negative detection polarity.
524 if factorNeg
is None:
526 self.log.info(
"Setting factor for negative detections equal to that for positive "
527 "detections: %f", factor)
528 results = pipeBase.Struct(positive=
None, negative=
None, factor=factor, factorNeg=factorNeg,
529 positiveThreshold=
None, negativeThreshold=
None)
531 if self.config.reEstimateBackground
or self.config.thresholdPolarity !=
"negative":
532 results.positiveThreshold = self.
makeThreshold(middle,
"positive", factor=factor)
533 results.positive = afwDet.FootprintSet(
535 results.positiveThreshold,
537 self.config.minPixels
539 results.positive.setRegion(bbox)
540 if self.config.reEstimateBackground
or self.config.thresholdPolarity !=
"positive":
541 results.negativeThreshold = self.
makeThreshold(middle,
"negative", factor=factorNeg)
542 results.negative = afwDet.FootprintSet(
544 results.negativeThreshold,
546 self.config.minPixels
548 results.negative.setRegion(bbox)
553 """Finalize the detected footprints.
555 Grows the footprints, sets the ``DETECTED`` and ``DETECTED_NEGATIVE``
556 mask planes,
and logs the results.
558 ``numPos`` (number of positive footprints), ``numPosPeaks`` (number
559 of positive peaks), ``numNeg`` (number of negative footprints),
560 ``numNegPeaks`` (number of negative peaks) entries are added to the
566 Mask image on which to flag detected pixels.
567 results : `lsst.pipe.base.Struct`
568 Struct of detection results, including ``positive``
and
569 ``negative`` entries; modified.
571 Gaussian sigma of PSF.
573 Multiplier
for the configured threshold. Note that this
is only
574 used here
for logging purposes.
575 factorNeg : `float`
or `
None`
576 Multiplier used
for the negative detection polarity threshold.
577 If `
None`, a factor equal to ``factor`` (i.e. equal to the one used
578 for positive detection polarity)
is assumed. Note that this
is only
579 used here
for logging purposes.
581 factorNeg = factor if factorNeg
is None else factorNeg
582 for polarity, maskName
in ((
"positive",
"DETECTED"), (
"negative",
"DETECTED_NEGATIVE")):
583 fpSet = getattr(results, polarity)
586 if self.config.nSigmaToGrow > 0:
587 nGrow = int((self.config.nSigmaToGrow * sigma) + 0.5)
588 self.metadata[
"nGrow"] = nGrow
589 if self.config.combinedGrow:
590 fpSet = afwDet.FootprintSet(fpSet, nGrow, self.config.isotropicGrow)
592 stencil = (afwGeom.Stencil.CIRCLE
if self.config.isotropicGrow
else
593 afwGeom.Stencil.MANHATTAN)
595 fp.dilate(nGrow, stencil)
596 fpSet.setMask(mask, maskName)
597 if not self.config.returnOriginalFootprints:
598 setattr(results, polarity, fpSet)
601 results.numPosPeaks = 0
603 results.numNegPeaks = 0
607 if results.positive
is not None:
608 results.numPos = len(results.positive.getFootprints())
609 results.numPosPeaks = sum(len(fp.getPeaks())
for fp
in results.positive.getFootprints())
610 positive =
" %d positive peaks in %d footprints" % (results.numPosPeaks, results.numPos)
611 if results.negative
is not None:
612 results.numNeg = len(results.negative.getFootprints())
613 results.numNegPeaks = sum(len(fp.getPeaks())
for fp
in results.negative.getFootprints())
614 negative =
" %d negative peaks in %d footprints" % (results.numNegPeaks, results.numNeg)
616 self.log.info(
"Detected%s%s%s to %g +ve and %g -ve %s",
617 positive,
" and" if positive
and negative
else "", negative,
618 self.config.thresholdValue*self.config.includeThresholdMultiplier*factor,
619 self.config.thresholdValue*self.config.includeThresholdMultiplier*factorNeg,
620 "DN" if self.config.thresholdType ==
"value" else "sigma")
623 """Estimate the background after detection
628 Image on which to estimate the background.
629 backgrounds : `lsst.afw.math.BackgroundList`
630 List of backgrounds; modified.
634 bg : `lsst.afw.math.backgroundMI`
635 Empirical background model.
637 bg = self.background.fitBackground(maskedImage)
638 if self.config.adjustBackground:
639 self.log.warning(
"Fiddling the background by %g", self.config.adjustBackground)
640 bg += self.config.adjustBackground
641 self.log.info(
"Resubtracting the background after object detection")
642 maskedImage -= bg.getImageF(self.background.config.algorithm,
643 self.background.config.undersampleStyle)
645 actrl = bg.getBackgroundControl().getApproximateControl()
646 backgrounds.append((bg, getattr(afwMath.Interpolate, self.background.config.algorithm),
647 bg.getAsUsedUndersampleStyle(), actrl.getStyle(), actrl.getOrderX(),
648 actrl.getOrderY(), actrl.getWeighting()))
652 """Clear unwanted results from the Struct of results
654 If we specifically want only positive or only negative detections,
655 drop the ones we don
't want, and its associated mask plane.
661 results : `lsst.pipe.base.Struct`
662 Detection results, with ``positive``
and ``negative`` elements;
665 if self.config.thresholdPolarity ==
"positive":
666 if self.config.reEstimateBackground:
667 mask &= ~mask.getPlaneBitMask(
"DETECTED_NEGATIVE")
668 results.negative =
None
669 elif self.config.thresholdPolarity ==
"negative":
670 if self.config.reEstimateBackground:
671 mask &= ~mask.getPlaneBitMask(
"DETECTED")
672 results.positive =
None
675 def detectFootprints(self, exposure, doSmooth=True, sigma=None, clearMask=True, expId=None):
676 """Detect footprints on an exposure.
681 Exposure to process; DETECTED{,_NEGATIVE} mask plane will be
683 doSmooth : `bool`, optional
684 If
True, smooth the image before detection using a Gaussian
685 of width ``sigma``,
or the measured PSF width of ``exposure``.
686 Set to
False when running on e.g. a pre-convolved image,
or a mask
688 sigma : `float`, optional
689 Gaussian Sigma of PSF (pixels); used
for smoothing
and to grow
690 detections;
if `
None` then measure the sigma of the PSF of the
692 clearMask : `bool`, optional
693 Clear both DETECTED
and DETECTED_NEGATIVE planes before running
695 expId : `dict`, optional
696 Exposure identifier; unused by this implementation, but used
for
697 RNG seed by subclasses.
701 results : `lsst.pipe.base.Struct`
702 A `~lsst.pipe.base.Struct` containing:
705 Positive polarity footprints.
708 Negative polarity footprints.
711 Number of footprints
in positive
or 0
if detection polarity was
714 Number of footprints
in negative
or 0
if detection polarity was
717 Re-estimated background. `
None`
if
718 ``reEstimateBackground==
False``.
719 (`lsst.afw.math.BackgroundList`)
721 Multiplication factor applied to the configured detection
724 maskedImage = exposure.maskedImage
729 psf = self.
getPsf(exposure, sigma=sigma)
731 convolveResults = self.
convolveImage(maskedImage, psf, doSmooth=doSmooth)
732 middle = convolveResults.middle
733 sigma = convolveResults.sigma
736 results.background = afwMath.BackgroundList()
737 if self.config.doTempLocalBackground:
744 results.positive = self.
setPeakSignificance(middle, results.positive, results.positiveThreshold)
745 results.negative = self.
setPeakSignificance(middle, results.negative, results.negativeThreshold,
748 if self.config.reEstimateBackground:
753 self.
display(exposure, results, middle)
758 """Set the significance of each detected peak to the pixel value divided
759 by the appropriate standard-deviation for ``config.thresholdType``.
761 Only sets significance
for "stdev" and "pixel_stdev" thresholdTypes;
762 we leave it undefined
for "value" and "variance" as it does
not have a
763 well-defined meaning
in those cases.
768 Exposure that footprints were detected on, likely the convolved,
769 local background-subtracted image.
771 Footprints detected on the image.
773 Threshold used to find footprints.
774 negative : `bool`, optional
775 Are we calculating
for negative sources?
777 if footprints
is None or footprints.getFootprints() == []:
779 polarity = -1
if negative
else 1
782 mapper = afwTable.SchemaMapper(footprints.getFootprints()[0].peaks.schema)
783 mapper.addMinimalSchema(footprints.getFootprints()[0].peaks.schema)
784 mapper.addOutputField(
"significance", type=float,
785 doc=
"Ratio of peak value to configured standard deviation.")
790 newFootprints = afwDet.FootprintSet(footprints)
791 for old, new
in zip(footprints.getFootprints(), newFootprints.getFootprints()):
792 newPeaks = afwDet.PeakCatalog(mapper.getOutputSchema())
793 newPeaks.extend(old.peaks, mapper=mapper)
794 new.getPeaks().clear()
795 new.setPeakCatalog(newPeaks)
798 if self.config.thresholdType ==
"pixel_stdev":
799 for footprint
in newFootprints.getFootprints():
800 footprint.updatePeakSignificance(exposure.variance, polarity)
801 elif self.config.thresholdType ==
"stdev":
802 sigma = threshold.getValue() / self.config.thresholdValue
803 for footprint
in newFootprints.getFootprints():
804 footprint.updatePeakSignificance(polarity*sigma)
806 for footprint
in newFootprints.getFootprints():
807 for peak
in footprint.peaks:
808 peak[
"significance"] = 0
813 """Make an afw.detection.Threshold object corresponding to the task's
814 configuration and the statistics of the given image.
819 Image to measure noise statistics
from if needed.
820 thresholdParity: `str`
821 One of
"positive" or "negative", to set the kind of fluctuations
822 the Threshold will detect.
824 Factor by which to multiply the configured detection threshold.
825 This
is useful
for tweaking the detection threshold slightly.
832 parity = False if thresholdParity ==
"negative" else True
833 thresholdValue = self.config.thresholdValue
834 thresholdType = self.config.thresholdType
835 if self.config.thresholdType ==
'stdev':
836 bad = image.getMask().getPlaneBitMask(self.config.statsMask)
837 sctrl = afwMath.StatisticsControl()
838 sctrl.setAndMask(bad)
839 stats = afwMath.makeStatistics(image, afwMath.STDEVCLIP, sctrl)
840 thresholdValue *= stats.getValue(afwMath.STDEVCLIP)
841 thresholdType =
'value'
843 threshold = afwDet.createThreshold(thresholdValue*factor, thresholdType, parity)
844 threshold.setIncludeMultiplier(self.config.includeThresholdMultiplier)
845 self.log.debug(
"Detection threshold: %s", threshold)
849 """Update the Peaks in a FootprintSet by detecting new Footprints and
850 Peaks in an image
and using the new Peaks instead of the old ones.
855 Set of Footprints whose Peaks should be updated.
857 Image to detect new Footprints
and Peak
in.
859 Threshold object
for detection.
861 Input Footprints
with fewer Peaks than self.config.nPeaksMaxSimple
862 are
not modified,
and if no new Peaks are detected
in an input
863 Footprint, the brightest original Peak
in that Footprint
is kept.
865 for footprint
in fpSet.getFootprints():
866 oldPeaks = footprint.getPeaks()
867 if len(oldPeaks) <= self.config.nPeaksMaxSimple:
872 sub = image.Factory(image, footprint.getBBox())
873 fpSetForPeaks = afwDet.FootprintSet(
877 self.config.minPixels
879 newPeaks = afwDet.PeakCatalog(oldPeaks.getTable())
880 for fpForPeaks
in fpSetForPeaks.getFootprints():
881 for peak
in fpForPeaks.getPeaks():
882 if footprint.contains(peak.getI()):
883 newPeaks.append(peak)
884 if len(newPeaks) > 0:
886 oldPeaks.extend(newPeaks)
892 """Set the edgeBitmask bits for all of maskedImage outside goodBBox
897 Image on which to set edge bits in the mask.
899 Bounding box of good pixels,
in ``LOCAL`` coordinates.
901 Bit mask to OR
with the existing mask bits
in the region
902 outside ``goodBBox``.
904 msk = maskedImage.getMask()
906 mx0, my0 = maskedImage.getXY0()
907 for x0, y0, w, h
in ([0, 0,
908 msk.getWidth(), goodBBox.getBeginY() - my0],
909 [0, goodBBox.getEndY() - my0, msk.getWidth(),
910 maskedImage.getHeight() - (goodBBox.getEndY() - my0)],
912 goodBBox.getBeginX() - mx0, msk.getHeight()],
913 [goodBBox.getEndX() - mx0, 0,
914 maskedImage.getWidth() - (goodBBox.getEndX() - mx0), msk.getHeight()],
918 edgeMask |= edgeBitmask
922 """Context manager for removing wide (large-scale) background
924 Removing a wide (large-scale) background helps to suppress the
925 detection of large footprints that may overwhelm the deblender.
926 It does, however, set a limit on the maximum scale of objects.
928 The background that we remove will be restored upon exit from
934 Exposure on which to remove large-scale background.
938 context : context manager
939 Context manager that will ensure the temporary wide background
942 doTempWideBackground = self.config.doTempWideBackground
943 if doTempWideBackground:
944 self.log.info(
"Applying temporary wide background subtraction")
945 original = exposure.maskedImage.image.array[:].copy()
946 self.tempWideBackground.
run(exposure).background
949 image = exposure.maskedImage.image
950 mask = exposure.maskedImage.mask
951 noData = mask.array & mask.getPlaneBitMask(
"NO_DATA") > 0
952 isGood = mask.array & mask.getPlaneBitMask(self.config.statsMask) == 0
953 image.array[noData] = np.median(image.array[~noData & isGood])
957 if doTempWideBackground:
958 exposure.maskedImage.image.array[:] = original
962 """Add a set of exposures together.
967 Sequence of exposures to add.
972 An exposure of the same size as each exposure
in ``exposureList``,
973 with the metadata
from ``exposureList[0]``
and a masked image equal
974 to the sum of all the exposure
's masked images.
976 exposure0 = exposureList[0]
977 image0 = exposure0.getMaskedImage()
979 addedImage = image0.Factory(image0, True)
980 addedImage.setXY0(image0.getXY0())
982 for exposure
in exposureList[1:]:
983 image = exposure.getMaskedImage()
986 addedExposure = exposure0.Factory(addedImage, exposure0.getWcs())
def tempWideBackgroundContext(self, exposure)
def getPsf(self, exposure, sigma=None)
def __init__(self, schema=None, **kwds)
def makeThreshold(self, image, thresholdParity, factor=1.0)
def setPeakSignificance(self, exposure, footprints, threshold, negative=False)
def run(self, table, exposure, doSmooth=True, sigma=None, clearMask=True, expId=None)
def convolveImage(self, maskedImage, psf, doSmooth=True)
def applyTempLocalBackground(self, exposure, middle, results)
def detectFootprints(self, exposure, doSmooth=True, sigma=None, clearMask=True, expId=None)
def reEstimateBackground(self, maskedImage, backgrounds)
def updatePeaks(self, fpSet, image, threshold)
def applyThreshold(self, middle, bbox, factor=1.0, factorNeg=None)
def setEdgeBits(maskedImage, goodBBox, edgeBitmask)
def clearUnwantedResults(self, mask, results)
def clearMask(self, mask)
def display(self, exposure, results, convolvedImage=None)
def calculateKernelSize(self, sigma)
def finalizeFootprints(self, mask, results, sigma, factor=1.0, factorNeg=None)
def addExposures(exposureList)