24__all__ = (
"SourceDetectionConfig",
"SourceDetectionTask",
"addExposures")
26from contextlib
import contextmanager
38import lsst.pipe.base
as pipeBase
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 """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`
233 The result resturned by `detectFootprints`
234 (`lsst.pipe.base.Struct`).
239 If flags.negative
is needed, but isn
't in table's schema.
240 lsst.pipe.base.TaskError
241 If sigma=
None, doSmooth=
True and the exposure has no PSF.
245 If you want to avoid dealing
with Sources
and Tables, you can use
249 raise ValueError(
"Table has incorrect Schema")
250 results = self.
detectFootprints(exposure=exposure, doSmooth=doSmooth, sigma=sigma,
251 clearMask=clearMask, expId=expId)
252 sources = afwTable.SourceCatalog(table)
253 sources.reserve(results.numPos + results.numNeg)
255 results.negative.makeSources(sources)
257 for record
in sources:
260 results.positive.makeSources(sources)
261 results.fpSets = results.copy()
262 results.sources = sources
265 def display(self, exposure, results, convolvedImage=None):
266 """Display detections if so configured
268 Displays the ``exposure`` in frame 0, overlays the detection peaks.
270 Requires that ``lsstDebug`` has been set up correctly, so that
271 ``
lsstDebug.Info(
"lsst.meas.algorithms.detection")`` evaluates `
True`.
273 If the ``convolvedImage``
is non-`
None`
and
275 ``convolvedImage`` will be displayed
in frame 1.
280 Exposure to display, on which will be plotted the detections.
281 results : `lsst.pipe.base.Struct`
282 Results of the
'detectFootprints' method, containing positive
and
283 negative footprints (which contain the peak positions that we will
284 plot). This
is a `Struct`
with ``positive``
and ``negative``
287 Convolved image used
for thresholding.
300 afwDisplay.setDefaultMaskTransparency(75)
302 disp0 = afwDisplay.Display(frame=0)
303 disp0.mtv(exposure, title=
"detection")
305 def plotPeaks(fps, ctype):
308 with disp0.Buffering():
309 for fp
in fps.getFootprints():
310 for pp
in fp.getPeaks():
311 disp0.dot(
"+", pp.getFx(), pp.getFy(), ctype=ctype)
312 plotPeaks(results.positive,
"yellow")
313 plotPeaks(results.negative,
"red")
315 if convolvedImage
and display > 1:
316 disp1 = afwDisplay.Display(frame=1)
317 disp1.mtv(convolvedImage, title=
"PSF smoothed")
319 disp2 = afwDisplay.Display(frame=2)
320 disp2.mtv(afwImage.ImageF(np.sqrt(exposure.variance.array)), title=
"stddev")
323 """Apply a temporary local background subtraction
325 This temporary local background serves to suppress noise fluctuations
326 in the wings of bright objects.
328 Peaks
in the footprints will be updated.
333 Exposure
for which to fit local background.
335 Convolved image on which detection will be performed
336 (typically smaller than ``exposure`` because the
337 half-kernel has been removed around the edges).
338 results : `lsst.pipe.base.Struct`
339 Results of the
'detectFootprints' method, containing positive
and
340 negative footprints (which contain the peak positions that we will
341 plot). This
is a `Struct`
with ``positive``
and ``negative``
347 bg = self.tempLocalBackground.fitBackground(exposure.getMaskedImage())
348 bgImage = bg.getImageF(self.tempLocalBackground.config.algorithm,
349 self.tempLocalBackground.config.undersampleStyle)
350 middle -= bgImage.Factory(bgImage, middle.getBBox())
351 if self.config.thresholdPolarity !=
"negative":
352 results.positiveThreshold = self.
makeThreshold(middle,
"positive")
353 self.
updatePeaks(results.positive, middle, results.positiveThreshold)
354 if self.config.thresholdPolarity !=
"positive":
355 results.negativeThreshold = self.
makeThreshold(middle,
"negative")
356 self.
updatePeaks(results.negative, middle, results.negativeThreshold)
359 """Clear the DETECTED and DETECTED_NEGATIVE mask planes
361 Removes any previous detection mask in preparation
for a new
369 mask &= ~(mask.getPlaneBitMask("DETECTED") | mask.getPlaneBitMask(
"DETECTED_NEGATIVE"))
372 """Calculate size of smoothing kernel
374 Uses the ``nSigmaForKernel`` configuration parameter. Note
375 that that is the full width of the kernel bounding box
376 (so a value of 7 means 3.5 sigma on either side of center).
377 The value will be rounded up to the nearest odd integer.
382 Gaussian sigma of smoothing kernel.
387 Size of the smoothing kernel.
389 return (int(sigma * self.config.nSigmaForKernel + 0.5)//2)*2 + 1
392 """Retrieve the PSF for an exposure
394 If ``sigma`` is provided, we make a ``GaussianPsf``
with that,
395 otherwise use the one
from the ``exposure``.
400 Exposure
from which to retrieve the PSF.
401 sigma : `float`, optional
402 Gaussian sigma to use
if provided.
407 PSF to use
for detection.
410 psf = exposure.getPsf()
412 raise RuntimeError(
"Unable to determine PSF to use for detection: no sigma provided")
413 sigma = psf.computeShape(psf.getAveragePosition()).getDeterminantRadius()
415 psf = afwDet.GaussianPsf(size, size, sigma)
419 """Convolve the image with the PSF
421 We convolve the image with a Gaussian approximation to the PSF,
422 because this
is separable
and therefore fast. It
's technically a
423 correlation rather than a convolution, but since we use a symmetric
424 Gaussian there's no difference.
426 The convolution can be disabled with ``doSmooth=
False``. If we do
427 convolve, we mask the edges
as ``EDGE``
and return the convolved image
428 with the edges removed. This
is because we can
't convolve the edges
429 because the kernel would extend off the image.
436 PSF to convolve with (actually
with a Gaussian approximation
439 Actually do the convolution? Set to
False when running on
440 e.g. a pre-convolved image,
or a mask plane.
442 Return Struct contents
443 ----------------------
445 Convolved image, without the edges.
447 Gaussian sigma used
for the convolution.
449 self.metadata["doSmooth"] = doSmooth
450 sigma = psf.computeShape(psf.getAveragePosition()).getDeterminantRadius()
451 self.metadata[
"sigma"] = sigma
454 middle = maskedImage.Factory(maskedImage, deep=
True)
455 return pipeBase.Struct(middle=middle, sigma=sigma)
460 self.metadata[
"smoothingKernelWidth"] = kWidth
461 gaussFunc = afwMath.GaussianFunction1D(sigma)
462 gaussKernel = afwMath.SeparableKernel(kWidth, kWidth, gaussFunc, gaussFunc)
464 convolvedImage = maskedImage.Factory(maskedImage.getBBox())
466 afwMath.convolve(convolvedImage, maskedImage, gaussKernel, afwMath.ConvolutionControl())
470 goodBBox = gaussKernel.shrinkBBox(convolvedImage.getBBox())
471 middle = convolvedImage.Factory(convolvedImage, goodBBox, afwImage.PARENT,
False)
475 self.
setEdgeBits(maskedImage, goodBBox, maskedImage.getMask().getPlaneBitMask(
"EDGE"))
477 return pipeBase.Struct(middle=middle, sigma=sigma)
480 r"""Apply thresholds to the convolved image
482 The threshold can be modified by the provided multiplication
488 Convolved image to threshold.
490 Bounding box of unconvolved image.
492 Multiplier for the configured threshold.
493 factorNeg : `float`
or `
None`
494 Multiplier
for the configured threshold
for negative detection polarity.
495 If `
None`, will be set equal to ``factor`` (i.e. equal to the factor used
496 for positive detection polarity).
500 results : `lsst.pipe.base.Struct`
501 The `~lsst.pipe.base.Struct` contains:
504 Positive detection footprints,
if configured.
507 Negative detection footprints,
if configured.
510 Multiplier
for the configured threshold.
513 Multiplier
for the configured threshold
for negative detection polarity.
516 if factorNeg
is None:
518 self.log.info(
"Setting factor for negative detections equal to that for positive "
519 "detections: %f", factor)
520 results = pipeBase.Struct(positive=
None, negative=
None, factor=factor, factorNeg=factorNeg,
521 positiveThreshold=
None, negativeThreshold=
None)
523 if self.config.reEstimateBackground
or self.config.thresholdPolarity !=
"negative":
524 results.positiveThreshold = self.
makeThreshold(middle,
"positive", factor=factor)
525 results.positive = afwDet.FootprintSet(
527 results.positiveThreshold,
529 self.config.minPixels
531 results.positive.setRegion(bbox)
532 if self.config.reEstimateBackground
or self.config.thresholdPolarity !=
"positive":
533 results.negativeThreshold = self.
makeThreshold(middle,
"negative", factor=factorNeg)
534 results.negative = afwDet.FootprintSet(
536 results.negativeThreshold,
538 self.config.minPixels
540 results.negative.setRegion(bbox)
545 """Finalize the detected footprints.
547 Grows the footprints, sets the ``DETECTED`` and ``DETECTED_NEGATIVE``
548 mask planes,
and logs the results.
550 ``numPos`` (number of positive footprints), ``numPosPeaks`` (number
551 of positive peaks), ``numNeg`` (number of negative footprints),
552 ``numNegPeaks`` (number of negative peaks) entries are added to the
558 Mask image on which to flag detected pixels.
559 results : `lsst.pipe.base.Struct`
560 Struct of detection results, including ``positive``
and
561 ``negative`` entries; modified.
563 Gaussian sigma of PSF.
565 Multiplier
for the configured threshold. Note that this
is only
566 used here
for logging purposes.
567 factorNeg : `float`
or `
None`
568 Multiplier used
for the negative detection polarity threshold.
569 If `
None`, a factor equal to ``factor`` (i.e. equal to the one used
570 for positive detection polarity)
is assumed. Note that this
is only
571 used here
for logging purposes.
573 factorNeg = factor if factorNeg
is None else factorNeg
574 for polarity, maskName
in ((
"positive",
"DETECTED"), (
"negative",
"DETECTED_NEGATIVE")):
575 fpSet = getattr(results, polarity)
578 if self.config.nSigmaToGrow > 0:
579 nGrow = int((self.config.nSigmaToGrow * sigma) + 0.5)
580 self.metadata[
"nGrow"] = nGrow
581 if self.config.combinedGrow:
582 fpSet = afwDet.FootprintSet(fpSet, nGrow, self.config.isotropicGrow)
584 stencil = (afwGeom.Stencil.CIRCLE
if self.config.isotropicGrow
else
585 afwGeom.Stencil.MANHATTAN)
587 fp.dilate(nGrow, stencil)
588 fpSet.setMask(mask, maskName)
589 if not self.config.returnOriginalFootprints:
590 setattr(results, polarity, fpSet)
593 results.numPosPeaks = 0
595 results.numNegPeaks = 0
599 if results.positive
is not None:
600 results.numPos = len(results.positive.getFootprints())
601 results.numPosPeaks = sum(len(fp.getPeaks())
for fp
in results.positive.getFootprints())
602 positive =
" %d positive peaks in %d footprints" % (results.numPosPeaks, results.numPos)
603 if results.negative
is not None:
604 results.numNeg = len(results.negative.getFootprints())
605 results.numNegPeaks = sum(len(fp.getPeaks())
for fp
in results.negative.getFootprints())
606 negative =
" %d negative peaks in %d footprints" % (results.numNegPeaks, results.numNeg)
608 self.log.info(
"Detected%s%s%s to %g +ve and %g -ve %s",
609 positive,
" and" if positive
and negative
else "", negative,
610 self.config.thresholdValue*self.config.includeThresholdMultiplier*factor,
611 self.config.thresholdValue*self.config.includeThresholdMultiplier*factorNeg,
612 "DN" if self.config.thresholdType ==
"value" else "sigma")
615 """Estimate the background after detection
620 Image on which to estimate the background.
621 backgrounds : `lsst.afw.math.BackgroundList`
622 List of backgrounds; modified.
626 bg : `lsst.afw.math.backgroundMI`
627 Empirical background model.
629 bg = self.background.fitBackground(maskedImage)
630 if self.config.adjustBackground:
631 self.log.warning(
"Fiddling the background by %g", self.config.adjustBackground)
632 bg += self.config.adjustBackground
633 self.log.info(
"Resubtracting the background after object detection")
634 maskedImage -= bg.getImageF(self.background.config.algorithm,
635 self.background.config.undersampleStyle)
637 actrl = bg.getBackgroundControl().getApproximateControl()
638 backgrounds.append((bg, getattr(afwMath.Interpolate, self.background.config.algorithm),
639 bg.getAsUsedUndersampleStyle(), actrl.getStyle(), actrl.getOrderX(),
640 actrl.getOrderY(), actrl.getWeighting()))
644 """Clear unwanted results from the Struct of results
646 If we specifically want only positive or only negative detections,
647 drop the ones we don
't want, and its associated mask plane.
653 results : `lsst.pipe.base.Struct`
654 Detection results, with ``positive``
and ``negative`` elements;
657 if self.config.thresholdPolarity ==
"positive":
658 if self.config.reEstimateBackground:
659 mask &= ~mask.getPlaneBitMask(
"DETECTED_NEGATIVE")
660 results.negative =
None
661 elif self.config.thresholdPolarity ==
"negative":
662 if self.config.reEstimateBackground:
663 mask &= ~mask.getPlaneBitMask(
"DETECTED")
664 results.positive =
None
667 def detectFootprints(self, exposure, doSmooth=True, sigma=None, clearMask=True, expId=None):
668 """Detect footprints on an exposure.
673 Exposure to process; DETECTED{,_NEGATIVE} mask plane will be
675 doSmooth : `bool`, optional
676 If
True, smooth the image before detection using a Gaussian
677 of width ``sigma``,
or the measured PSF width of ``exposure``.
678 Set to
False when running on e.g. a pre-convolved image,
or a mask
680 sigma : `float`, optional
681 Gaussian Sigma of PSF (pixels); used
for smoothing
and to grow
682 detections;
if `
None` then measure the sigma of the PSF of the
684 clearMask : `bool`, optional
685 Clear both DETECTED
and DETECTED_NEGATIVE planes before running
687 expId : `dict`, optional
688 Exposure identifier; unused by this implementation, but used
for
689 RNG seed by subclasses.
691 Return Struct contents
692 ----------------------
694 Positive polarity footprints (may be `
None`)
696 Negative polarity footprints (may be `
None`)
698 Number of footprints
in positive
or 0
if detection polarity was
701 Number of footprints
in negative
or 0
if detection polarity was
703 background : `lsst.afw.math.BackgroundList`
704 Re-estimated background. `
None`
if
705 ``reEstimateBackground==
False``.
707 Multiplication factor applied to the configured detection
710 maskedImage = exposure.maskedImage
715 psf = self.
getPsf(exposure, sigma=sigma)
717 convolveResults = self.
convolveImage(maskedImage, psf, doSmooth=doSmooth)
718 middle = convolveResults.middle
719 sigma = convolveResults.sigma
722 results.background = afwMath.BackgroundList()
723 if self.config.doTempLocalBackground:
730 results.positive = self.
setPeakSignificance(middle, results.positive, results.positiveThreshold)
731 results.negative = self.
setPeakSignificance(middle, results.negative, results.negativeThreshold,
734 if self.config.reEstimateBackground:
739 self.
display(exposure, results, middle)
744 """Set the significance of each detected peak to the pixel value divided
745 by the appropriate standard-deviation for ``config.thresholdType``.
747 Only sets significance
for "stdev" and "pixel_stdev" thresholdTypes;
748 we leave it undefined
for "value" and "variance" as it does
not have a
749 well-defined meaning
in those cases.
754 Exposure that footprints were detected on, likely the convolved,
755 local background-subtracted image.
757 Footprints detected on the image.
759 Threshold used to find footprints.
760 negative : `bool`, optional
761 Are we calculating
for negative sources?
763 if footprints
is None or footprints.getFootprints() == []:
765 polarity = -1
if negative
else 1
768 mapper = afwTable.SchemaMapper(footprints.getFootprints()[0].peaks.schema)
769 mapper.addMinimalSchema(footprints.getFootprints()[0].peaks.schema)
770 mapper.addOutputField(
"significance", type=float,
771 doc=
"Ratio of peak value to configured standard deviation.")
776 newFootprints = afwDet.FootprintSet(footprints)
777 for old, new
in zip(footprints.getFootprints(), newFootprints.getFootprints()):
778 newPeaks = afwDet.PeakCatalog(mapper.getOutputSchema())
779 newPeaks.extend(old.peaks, mapper=mapper)
780 new.getPeaks().clear()
781 new.setPeakCatalog(newPeaks)
784 if self.config.thresholdType ==
"pixel_stdev":
785 for footprint
in newFootprints.getFootprints():
786 footprint.updatePeakSignificance(exposure.variance, polarity)
787 elif self.config.thresholdType ==
"stdev":
788 sigma = threshold.getValue() / self.config.thresholdValue
789 for footprint
in newFootprints.getFootprints():
790 footprint.updatePeakSignificance(polarity*sigma)
792 for footprint
in newFootprints.getFootprints():
793 for peak
in footprint.peaks:
794 peak[
"significance"] = 0
799 """Make an afw.detection.Threshold object corresponding to the task's
800 configuration and the statistics of the given image.
805 Image to measure noise statistics
from if needed.
806 thresholdParity: `str`
807 One of
"positive" or "negative", to set the kind of fluctuations
808 the Threshold will detect.
810 Factor by which to multiply the configured detection threshold.
811 This
is useful
for tweaking the detection threshold slightly.
818 parity = False if thresholdParity ==
"negative" else True
819 thresholdValue = self.config.thresholdValue
820 thresholdType = self.config.thresholdType
821 if self.config.thresholdType ==
'stdev':
822 bad = image.getMask().getPlaneBitMask(self.config.statsMask)
823 sctrl = afwMath.StatisticsControl()
824 sctrl.setAndMask(bad)
825 stats = afwMath.makeStatistics(image, afwMath.STDEVCLIP, sctrl)
826 thresholdValue *= stats.getValue(afwMath.STDEVCLIP)
827 thresholdType =
'value'
829 threshold = afwDet.createThreshold(thresholdValue*factor, thresholdType, parity)
830 threshold.setIncludeMultiplier(self.config.includeThresholdMultiplier)
831 self.log.debug(
"Detection threshold: %s", threshold)
835 """Update the Peaks in a FootprintSet by detecting new Footprints and
836 Peaks in an image
and using the new Peaks instead of the old ones.
841 Set of Footprints whose Peaks should be updated.
843 Image to detect new Footprints
and Peak
in.
845 Threshold object
for detection.
847 Input Footprints
with fewer Peaks than self.config.nPeaksMaxSimple
848 are
not modified,
and if no new Peaks are detected
in an input
849 Footprint, the brightest original Peak
in that Footprint
is kept.
851 for footprint
in fpSet.getFootprints():
852 oldPeaks = footprint.getPeaks()
853 if len(oldPeaks) <= self.config.nPeaksMaxSimple:
858 sub = image.Factory(image, footprint.getBBox())
859 fpSetForPeaks = afwDet.FootprintSet(
863 self.config.minPixels
865 newPeaks = afwDet.PeakCatalog(oldPeaks.getTable())
866 for fpForPeaks
in fpSetForPeaks.getFootprints():
867 for peak
in fpForPeaks.getPeaks():
868 if footprint.contains(peak.getI()):
869 newPeaks.append(peak)
870 if len(newPeaks) > 0:
872 oldPeaks.extend(newPeaks)
878 """Set the edgeBitmask bits for all of maskedImage outside goodBBox
883 Image on which to set edge bits in the mask.
885 Bounding box of good pixels,
in ``LOCAL`` coordinates.
887 Bit mask to OR
with the existing mask bits
in the region
888 outside ``goodBBox``.
890 msk = maskedImage.getMask()
892 mx0, my0 = maskedImage.getXY0()
893 for x0, y0, w, h
in ([0, 0,
894 msk.getWidth(), goodBBox.getBeginY() - my0],
895 [0, goodBBox.getEndY() - my0, msk.getWidth(),
896 maskedImage.getHeight() - (goodBBox.getEndY() - my0)],
898 goodBBox.getBeginX() - mx0, msk.getHeight()],
899 [goodBBox.getEndX() - mx0, 0,
900 maskedImage.getWidth() - (goodBBox.getEndX() - mx0), msk.getHeight()],
904 edgeMask |= edgeBitmask
908 """Context manager for removing wide (large-scale) background
910 Removing a wide (large-scale) background helps to suppress the
911 detection of large footprints that may overwhelm the deblender.
912 It does, however, set a limit on the maximum scale of objects.
914 The background that we remove will be restored upon exit from
920 Exposure on which to remove large-scale background.
924 context : context manager
925 Context manager that will ensure the temporary wide background
928 doTempWideBackground = self.config.doTempWideBackground
929 if doTempWideBackground:
930 self.log.info(
"Applying temporary wide background subtraction")
931 original = exposure.maskedImage.image.array[:].copy()
932 self.tempWideBackground.
run(exposure).background
935 image = exposure.maskedImage.image
936 mask = exposure.maskedImage.mask
937 noData = mask.array & mask.getPlaneBitMask(
"NO_DATA") > 0
938 isGood = mask.array & mask.getPlaneBitMask(self.config.statsMask) == 0
939 image.array[noData] = np.median(image.array[~noData & isGood])
943 if doTempWideBackground:
944 exposure.maskedImage.image.array[:] = original
948 """Add a set of exposures together.
953 Sequence of exposures to add.
958 An exposure of the same size as each exposure
in ``exposureList``,
959 with the metadata
from ``exposureList[0]``
and a masked image equal
960 to the sum of all the exposure
's masked images.
962 exposure0 = exposureList[0]
963 image0 = exposure0.getMaskedImage()
965 addedImage = image0.Factory(image0, True)
966 addedImage.setXY0(image0.getXY0())
968 for exposure
in exposureList[1:]:
969 image = exposure.getMaskedImage()
972 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)