24 __all__ = (
"SourceDetectionConfig",
"SourceDetectionTask",
"addExposures")
26 from contextlib
import contextmanager
27 from deprecated.sphinx
import deprecated
38 import lsst.pex.config
as pexConfig
40 from .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"):
171 @anchor SourceDetectionTask_
173 @brief Detect positive and negative sources on an exposure and return a new @link table.SourceCatalog@endlink.
175 @section meas_algorithms_detection_Contents Contents
177 - @ref meas_algorithms_detection_Purpose
178 - @ref meas_algorithms_detection_Initialize
179 - @ref meas_algorithms_detection_Invoke
180 - @ref meas_algorithms_detection_Config
181 - @ref meas_algorithms_detection_Debug
182 - @ref meas_algorithms_detection_Example
184 @section meas_algorithms_detection_Purpose Description
186 @copybrief SourceDetectionTask
188 @section meas_algorithms_detection_Initialize Task initialisation
190 @copydoc \_\_init\_\_
192 @section meas_algorithms_detection_Invoke Invoking the Task
196 @section meas_algorithms_detection_Config Configuration parameters
198 See @ref SourceDetectionConfig
200 @section meas_algorithms_detection_Debug Debug variables
202 The @link lsst.pipe.base.cmdLineTask.CmdLineTask command line task@endlink interface supports a
203 flag @c -d to import @b debug.py from your @c PYTHONPATH; see @ref baseDebug for more about @b debug.py files.
205 The available variables in SourceDetectionTask are:
209 - If True, display the exposure on afwDisplay.Display's frame 0.
210 +ve detections in blue, -ve detections in cyan
211 - If display > 1, display the convolved exposure on frame 1
214 @section meas_algorithms_detection_Example A complete example of using SourceDetectionTask
216 This code is in @link measAlgTasks.py@endlink in the examples directory, and can be run as @em e.g.
218 examples/measAlgTasks.py --doDisplay
220 @dontinclude measAlgTasks.py
221 The example also runs the SingleFrameMeasurementTask; see @ref meas_algorithms_measurement_Example for more
224 Import the task (there are some other standard imports; read the file if you're confused)
225 @skipline SourceDetectionTask
227 We need to create our task before processing any data as the task constructor
228 can add an extra column to the schema, but first we need an almost-empty Schema
229 @skipline makeMinimalSchema
230 after which we can call the constructor:
231 @skip SourceDetectionTask.ConfigClass
234 We're now ready to process the data (we could loop over multiple exposures/catalogues using the same
235 task objects). First create the output table:
238 And process the image
240 (You may not be happy that the threshold was set in the config before creating the Task rather than being set
241 separately for each exposure. You @em can reset it just before calling the run method if you must, but we
242 should really implement a better solution).
244 We can then unpack and use the results:
249 To investigate the @ref meas_algorithms_detection_Debug, put something like
253 di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively
254 if name == "lsst.meas.algorithms.detection":
259 lsstDebug.Info = DebugInfo
261 into your debug.py file and run measAlgTasks.py with the @c --debug flag.
263 ConfigClass = SourceDetectionConfig
264 _DefaultName =
"sourceDetection"
267 """!Create the detection task. Most arguments are simply passed onto pipe.base.Task.
269 @param schema An lsst::afw::table::Schema used to create the output lsst.afw.table.SourceCatalog
270 @param **kwds Keyword arguments passed to lsst.pipe.base.task.Task.__init__.
272 If schema is not None and configured for 'both' detections,
273 a 'flags.negative' field will be added to label detections made with a
276 @note This task can add fields to the schema, so any code calling this task must ensure that
277 these columns are indeed present in the input match list; see @ref Example
279 pipeBase.Task.__init__(self, **kwds)
280 if schema
is not None and self.config.thresholdPolarity ==
"both":
282 "flags_negative", type=
"Flag",
283 doc=
"set if source was detected as significantly negative"
286 if self.config.thresholdPolarity ==
"both":
287 self.log.
warn(
"Detection polarity set to 'both', but no flag will be "
288 "set to distinguish between positive and negative detections")
290 if self.config.reEstimateBackground:
291 self.makeSubtask(
"background")
292 if self.config.doTempLocalBackground:
293 self.makeSubtask(
"tempLocalBackground")
294 if self.config.doTempWideBackground:
295 self.makeSubtask(
"tempWideBackground")
298 def run(self, table, exposure, doSmooth=True, sigma=None, clearMask=True, expId=None):
299 """Run source detection and create a SourceCatalog of detections.
303 table : `lsst.afw.table.SourceTable`
304 Table object that will be used to create the SourceCatalog.
305 exposure : `lsst.afw.image.Exposure`
306 Exposure to process; DETECTED mask plane will be set in-place.
308 If True, smooth the image before detection using a Gaussian of width
309 ``sigma``, or the measured PSF width. Set to False when running on
310 e.g. a pre-convolved image, or a mask plane.
312 Sigma of PSF (pixels); used for smoothing and to grow detections;
313 if None then measure the sigma of the PSF of the exposure
315 Clear DETECTED{,_NEGATIVE} planes before running detection.
317 Exposure identifier; unused by this implementation, but used for
318 RNG seed by subclasses.
322 result : `lsst.pipe.base.Struct`
324 The detected sources (`lsst.afw.table.SourceCatalog`)
326 The result resturned by `detectFootprints`
327 (`lsst.pipe.base.Struct`).
332 If flags.negative is needed, but isn't in table's schema.
333 lsst.pipe.base.TaskError
334 If sigma=None, doSmooth=True and the exposure has no PSF.
338 If you want to avoid dealing with Sources and Tables, you can use
339 detectFootprints() to just get the `lsst.afw.detection.FootprintSet`s.
342 raise ValueError(
"Table has incorrect Schema")
343 results = self.
detectFootprints(exposure=exposure, doSmooth=doSmooth, sigma=sigma,
344 clearMask=clearMask, expId=expId)
346 sources.reserve(results.numPos + results.numNeg)
348 results.negative.makeSources(sources)
350 for record
in sources:
353 results.positive.makeSources(sources)
354 results.fpSets = results.copy()
355 results.sources = sources
358 @deprecated(reason=
"Replaced by SourceDetectionTask.run(). Will be removed after v20.",
359 category=FutureWarning)
361 return self.
run(*args, **kwargs)
363 def display(self, exposure, results, convolvedImage=None):
364 """Display detections if so configured
366 Displays the ``exposure`` in frame 0, overlays the detection peaks.
368 Requires that ``lsstDebug`` has been set up correctly, so that
369 ``lsstDebug.Info("lsst.meas.algorithms.detection")`` evaluates `True`.
371 If the ``convolvedImage`` is non-`None` and
372 ``lsstDebug.Info("lsst.meas.algorithms.detection") > 1``, the
373 ``convolvedImage`` will be displayed in frame 1.
377 exposure : `lsst.afw.image.Exposure`
378 Exposure to display, on which will be plotted the detections.
379 results : `lsst.pipe.base.Struct`
380 Results of the 'detectFootprints' method, containing positive and
381 negative footprints (which contain the peak positions that we will
382 plot). This is a `Struct` with ``positive`` and ``negative``
383 elements that are of type `lsst.afw.detection.FootprintSet`.
384 convolvedImage : `lsst.afw.image.Image`, optional
385 Convolved image used for thresholding.
398 afwDisplay.setDefaultMaskTransparency(75)
400 disp0 = afwDisplay.Display(frame=0)
401 disp0.mtv(exposure, title=
"detection")
403 def plotPeaks(fps, ctype):
406 with disp0.Buffering():
407 for fp
in fps.getFootprints():
408 for pp
in fp.getPeaks():
409 disp0.dot(
"+", pp.getFx(), pp.getFy(), ctype=ctype)
410 plotPeaks(results.positive,
"yellow")
411 plotPeaks(results.negative,
"red")
413 if convolvedImage
and display > 1:
414 disp1 = afwDisplay.Display(frame=1)
415 disp1.mtv(convolvedImage, title=
"PSF smoothed")
418 """Apply a temporary local background subtraction
420 This temporary local background serves to suppress noise fluctuations
421 in the wings of bright objects.
423 Peaks in the footprints will be updated.
427 exposure : `lsst.afw.image.Exposure`
428 Exposure for which to fit local background.
429 middle : `lsst.afw.image.MaskedImage`
430 Convolved image on which detection will be performed
431 (typically smaller than ``exposure`` because the
432 half-kernel has been removed around the edges).
433 results : `lsst.pipe.base.Struct`
434 Results of the 'detectFootprints' method, containing positive and
435 negative footprints (which contain the peak positions that we will
436 plot). This is a `Struct` with ``positive`` and ``negative``
437 elements that are of type `lsst.afw.detection.FootprintSet`.
442 bg = self.tempLocalBackground.fitBackground(exposure.getMaskedImage())
443 bgImage = bg.getImageF(self.tempLocalBackground.config.algorithm,
444 self.tempLocalBackground.config.undersampleStyle)
445 middle -= bgImage.Factory(bgImage, middle.getBBox())
448 if self.config.thresholdPolarity !=
"negative":
449 self.
updatePeaks(results.positive, middle, thresholdPos)
450 if self.config.thresholdPolarity !=
"positive":
451 self.
updatePeaks(results.negative, middle, thresholdNeg)
454 """Clear the DETECTED and DETECTED_NEGATIVE mask planes
456 Removes any previous detection mask in preparation for a new
461 mask : `lsst.afw.image.Mask`
464 mask &= ~(mask.getPlaneBitMask(
"DETECTED") | mask.getPlaneBitMask(
"DETECTED_NEGATIVE"))
467 """Calculate size of smoothing kernel
469 Uses the ``nSigmaForKernel`` configuration parameter. Note
470 that that is the full width of the kernel bounding box
471 (so a value of 7 means 3.5 sigma on either side of center).
472 The value will be rounded up to the nearest odd integer.
477 Gaussian sigma of smoothing kernel.
482 Size of the smoothing kernel.
484 return (int(sigma * self.config.nSigmaForKernel + 0.5)//2)*2 + 1
487 """Retrieve the PSF for an exposure
489 If ``sigma`` is provided, we make a ``GaussianPsf`` with that,
490 otherwise use the one from the ``exposure``.
494 exposure : `lsst.afw.image.Exposure`
495 Exposure from which to retrieve the PSF.
496 sigma : `float`, optional
497 Gaussian sigma to use if provided.
501 psf : `lsst.afw.detection.Psf`
502 PSF to use for detection.
505 psf = exposure.getPsf()
507 raise RuntimeError(
"Unable to determine PSF to use for detection: no sigma provided")
508 sigma = psf.computeShape().getDeterminantRadius()
514 """Convolve the image with the PSF
516 We convolve the image with a Gaussian approximation to the PSF,
517 because this is separable and therefore fast. It's technically a
518 correlation rather than a convolution, but since we use a symmetric
519 Gaussian there's no difference.
521 The convolution can be disabled with ``doSmooth=False``. If we do
522 convolve, we mask the edges as ``EDGE`` and return the convolved image
523 with the edges removed. This is because we can't convolve the edges
524 because the kernel would extend off the image.
528 maskedImage : `lsst.afw.image.MaskedImage`
530 psf : `lsst.afw.detection.Psf`
531 PSF to convolve with (actually with a Gaussian approximation
534 Actually do the convolution? Set to False when running on
535 e.g. a pre-convolved image, or a mask plane.
537 Return Struct contents
538 ----------------------
539 middle : `lsst.afw.image.MaskedImage`
540 Convolved image, without the edges.
542 Gaussian sigma used for the convolution.
544 self.metadata.
set(
"doSmooth", doSmooth)
545 sigma = psf.computeShape().getDeterminantRadius()
546 self.metadata.
set(
"sigma", sigma)
549 middle = maskedImage.Factory(maskedImage)
550 return pipeBase.Struct(middle=middle, sigma=sigma)
555 self.metadata.
set(
"smoothingKernelWidth", kWidth)
556 gaussFunc = afwMath.GaussianFunction1D(sigma)
559 convolvedImage = maskedImage.Factory(maskedImage.getBBox())
565 goodBBox = gaussKernel.shrinkBBox(convolvedImage.getBBox())
566 middle = convolvedImage.Factory(convolvedImage, goodBBox, afwImage.PARENT,
False)
570 self.
setEdgeBits(maskedImage, goodBBox, maskedImage.getMask().getPlaneBitMask(
"EDGE"))
572 return pipeBase.Struct(middle=middle, sigma=sigma)
575 """Apply thresholds to the convolved image
577 Identifies ``Footprint``s, both positive and negative.
579 The threshold can be modified by the provided multiplication
584 middle : `lsst.afw.image.MaskedImage`
585 Convolved image to threshold.
586 bbox : `lsst.geom.Box2I`
587 Bounding box of unconvolved image.
589 Multiplier for the configured threshold.
591 Return Struct contents
592 ----------------------
593 positive : `lsst.afw.detection.FootprintSet` or `None`
594 Positive detection footprints, if configured.
595 negative : `lsst.afw.detection.FootprintSet` or `None`
596 Negative detection footprints, if configured.
598 Multiplier for the configured threshold.
600 results = pipeBase.Struct(positive=
None, negative=
None, factor=factor)
602 if self.config.reEstimateBackground
or self.config.thresholdPolarity !=
"negative":
603 threshold = self.
makeThreshold(middle,
"positive", factor=factor)
608 self.config.minPixels
610 results.positive.setRegion(bbox)
611 if self.config.reEstimateBackground
or self.config.thresholdPolarity !=
"positive":
612 threshold = self.
makeThreshold(middle,
"negative", factor=factor)
617 self.config.minPixels
619 results.negative.setRegion(bbox)
624 """Finalize the detected footprints
626 Grows the footprints, sets the ``DETECTED`` and ``DETECTED_NEGATIVE``
627 mask planes, and logs the results.
629 ``numPos`` (number of positive footprints), ``numPosPeaks`` (number
630 of positive peaks), ``numNeg`` (number of negative footprints),
631 ``numNegPeaks`` (number of negative peaks) entries are added to the
636 mask : `lsst.afw.image.Mask`
637 Mask image on which to flag detected pixels.
638 results : `lsst.pipe.base.Struct`
639 Struct of detection results, including ``positive`` and
640 ``negative`` entries; modified.
642 Gaussian sigma of PSF.
644 Multiplier for the configured threshold.
646 for polarity, maskName
in ((
"positive",
"DETECTED"), (
"negative",
"DETECTED_NEGATIVE")):
647 fpSet = getattr(results, polarity)
650 if self.config.nSigmaToGrow > 0:
651 nGrow = int((self.config.nSigmaToGrow * sigma) + 0.5)
652 self.metadata.
set(
"nGrow", nGrow)
653 if self.config.combinedGrow:
656 stencil = (afwGeom.Stencil.CIRCLE
if self.config.isotropicGrow
else
657 afwGeom.Stencil.MANHATTAN)
659 fp.dilate(nGrow, stencil)
660 fpSet.setMask(mask, maskName)
661 if not self.config.returnOriginalFootprints:
662 setattr(results, polarity, fpSet)
665 results.numPosPeaks = 0
667 results.numNegPeaks = 0
671 if results.positive
is not None:
672 results.numPos = len(results.positive.getFootprints())
673 results.numPosPeaks = sum(len(fp.getPeaks())
for fp
in results.positive.getFootprints())
674 positive =
" %d positive peaks in %d footprints" % (results.numPosPeaks, results.numPos)
675 if results.negative
is not None:
676 results.numNeg = len(results.negative.getFootprints())
677 results.numNegPeaks = sum(len(fp.getPeaks())
for fp
in results.negative.getFootprints())
678 negative =
" %d negative peaks in %d footprints" % (results.numNegPeaks, results.numNeg)
680 self.log.
info(
"Detected%s%s%s to %g %s" %
681 (positive,
" and" if positive
and negative
else "", negative,
682 self.config.thresholdValue*self.config.includeThresholdMultiplier*factor,
683 "DN" if self.config.thresholdType ==
"value" else "sigma"))
686 """Estimate the background after detection
690 maskedImage : `lsst.afw.image.MaskedImage`
691 Image on which to estimate the background.
692 backgrounds : `lsst.afw.math.BackgroundList`
693 List of backgrounds; modified.
697 bg : `lsst.afw.math.backgroundMI`
698 Empirical background model.
700 bg = self.background.fitBackground(maskedImage)
701 if self.config.adjustBackground:
702 self.log.
warn(
"Fiddling the background by %g", self.config.adjustBackground)
703 bg += self.config.adjustBackground
704 self.log.
info(
"Resubtracting the background after object detection")
705 maskedImage -= bg.getImageF(self.background.config.algorithm,
706 self.background.config.undersampleStyle)
708 actrl = bg.getBackgroundControl().getApproximateControl()
710 bg.getAsUsedUndersampleStyle(), actrl.getStyle(), actrl.getOrderX(),
711 actrl.getOrderY(), actrl.getWeighting()))
715 """Clear unwanted results from the Struct of results
717 If we specifically want only positive or only negative detections,
718 drop the ones we don't want, and its associated mask plane.
722 mask : `lsst.afw.image.Mask`
724 results : `lsst.pipe.base.Struct`
725 Detection results, with ``positive`` and ``negative`` elements;
728 if self.config.thresholdPolarity ==
"positive":
729 if self.config.reEstimateBackground:
730 mask &= ~mask.getPlaneBitMask(
"DETECTED_NEGATIVE")
731 results.negative =
None
732 elif self.config.thresholdPolarity ==
"negative":
733 if self.config.reEstimateBackground:
734 mask &= ~mask.getPlaneBitMask(
"DETECTED")
735 results.positive =
None
738 def detectFootprints(self, exposure, doSmooth=True, sigma=None, clearMask=True, expId=None):
739 """Detect footprints on an exposure.
743 exposure : `lsst.afw.image.Exposure`
744 Exposure to process; DETECTED{,_NEGATIVE} mask plane will be
746 doSmooth : `bool`, optional
747 If True, smooth the image before detection using a Gaussian
748 of width ``sigma``, or the measured PSF width of ``exposure``.
749 Set to False when running on e.g. a pre-convolved image, or a mask
751 sigma : `float`, optional
752 Gaussian Sigma of PSF (pixels); used for smoothing and to grow
753 detections; if `None` then measure the sigma of the PSF of the
755 clearMask : `bool`, optional
756 Clear both DETECTED and DETECTED_NEGATIVE planes before running
758 expId : `dict`, optional
759 Exposure identifier; unused by this implementation, but used for
760 RNG seed by subclasses.
762 Return Struct contents
763 ----------------------
764 positive : `lsst.afw.detection.FootprintSet`
765 Positive polarity footprints (may be `None`)
766 negative : `lsst.afw.detection.FootprintSet`
767 Negative polarity footprints (may be `None`)
769 Number of footprints in positive or 0 if detection polarity was
772 Number of footprints in negative or 0 if detection polarity was
774 background : `lsst.afw.math.BackgroundList`
775 Re-estimated background. `None` if
776 ``reEstimateBackground==False``.
778 Multiplication factor applied to the configured detection
781 maskedImage = exposure.maskedImage
786 psf = self.
getPsf(exposure, sigma=sigma)
788 convolveResults = self.
convolveImage(maskedImage, psf, doSmooth=doSmooth)
789 middle = convolveResults.middle
790 sigma = convolveResults.sigma
794 if self.config.doTempLocalBackground:
798 if self.config.reEstimateBackground:
802 self.
display(exposure, results, middle)
807 """Make an afw.detection.Threshold object corresponding to the task's
808 configuration and the statistics of the given image.
812 image : `afw.image.MaskedImage`
813 Image to measure noise statistics from if needed.
814 thresholdParity: `str`
815 One of "positive" or "negative", to set the kind of fluctuations
816 the Threshold will detect.
818 Factor by which to multiply the configured detection threshold.
819 This is useful for tweaking the detection threshold slightly.
823 threshold : `lsst.afw.detection.Threshold`
826 parity =
False if thresholdParity ==
"negative" else True
827 thresholdValue = self.config.thresholdValue
828 thresholdType = self.config.thresholdType
829 if self.config.thresholdType ==
'stdev':
830 bad = image.getMask().getPlaneBitMask(self.config.statsMask)
832 sctrl.setAndMask(bad)
834 thresholdValue *= stats.getValue(afwMath.STDEVCLIP)
835 thresholdType =
'value'
838 threshold.setIncludeMultiplier(self.config.includeThresholdMultiplier)
842 """Update the Peaks in a FootprintSet by detecting new Footprints and
843 Peaks in an image and using the new Peaks instead of the old ones.
847 fpSet : `afw.detection.FootprintSet`
848 Set of Footprints whose Peaks should be updated.
849 image : `afw.image.MaskedImage`
850 Image to detect new Footprints and Peak in.
851 threshold : `afw.detection.Threshold`
852 Threshold object for detection.
854 Input Footprints with fewer Peaks than self.config.nPeaksMaxSimple
855 are not modified, and if no new Peaks are detected in an input
856 Footprint, the brightest original Peak in that Footprint is kept.
858 for footprint
in fpSet.getFootprints():
859 oldPeaks = footprint.getPeaks()
860 if len(oldPeaks) <= self.config.nPeaksMaxSimple:
865 sub = image.Factory(image, footprint.getBBox())
870 self.config.minPixels
873 for fpForPeaks
in fpSetForPeaks.getFootprints():
874 for peak
in fpForPeaks.getPeaks():
875 if footprint.contains(peak.getI()):
876 newPeaks.append(peak)
877 if len(newPeaks) > 0:
879 oldPeaks.extend(newPeaks)
885 """Set the edgeBitmask bits for all of maskedImage outside goodBBox
889 maskedImage : `lsst.afw.image.MaskedImage`
890 Image on which to set edge bits in the mask.
891 goodBBox : `lsst.geom.Box2I`
892 Bounding box of good pixels, in ``LOCAL`` coordinates.
893 edgeBitmask : `lsst.afw.image.MaskPixel`
894 Bit mask to OR with the existing mask bits in the region
895 outside ``goodBBox``.
897 msk = maskedImage.getMask()
899 mx0, my0 = maskedImage.getXY0()
900 for x0, y0, w, h
in ([0, 0,
901 msk.getWidth(), goodBBox.getBeginY() - my0],
902 [0, goodBBox.getEndY() - my0, msk.getWidth(),
903 maskedImage.getHeight() - (goodBBox.getEndY() - my0)],
905 goodBBox.getBeginX() - mx0, msk.getHeight()],
906 [goodBBox.getEndX() - mx0, 0,
907 maskedImage.getWidth() - (goodBBox.getEndX() - mx0), msk.getHeight()],
911 edgeMask |= edgeBitmask
915 """Context manager for removing wide (large-scale) background
917 Removing a wide (large-scale) background helps to suppress the
918 detection of large footprints that may overwhelm the deblender.
919 It does, however, set a limit on the maximum scale of objects.
921 The background that we remove will be restored upon exit from
926 exposure : `lsst.afw.image.Exposure`
927 Exposure on which to remove large-scale background.
931 context : context manager
932 Context manager that will ensure the temporary wide background
935 doTempWideBackground = self.config.doTempWideBackground
936 if doTempWideBackground:
937 self.log.
info(
"Applying temporary wide background subtraction")
938 original = exposure.maskedImage.image.array[:].copy()
939 self.tempWideBackground.
run(exposure).background
942 image = exposure.maskedImage.image
943 mask = exposure.maskedImage.mask
944 noData = mask.array & mask.getPlaneBitMask(
"NO_DATA") > 0
945 isGood = mask.array & mask.getPlaneBitMask(self.config.statsMask) == 0
946 image.array[noData] = np.median(image.array[~noData & isGood])
950 if doTempWideBackground:
951 exposure.maskedImage.image.array[:] = original
955 """Add a set of exposures together.
959 exposureList : `list` of `lsst.afw.image.Exposure`
960 Sequence of exposures to add.
964 addedExposure : `lsst.afw.image.Exposure`
965 An exposure of the same size as each exposure in ``exposureList``,
966 with the metadata from ``exposureList[0]`` and a masked image equal
967 to the sum of all the exposure's masked images.
969 exposure0 = exposureList[0]
970 image0 = exposure0.getMaskedImage()
972 addedImage = image0.Factory(image0,
True)
973 addedImage.setXY0(image0.getXY0())
975 for exposure
in exposureList[1:]:
976 image = exposure.getMaskedImage()
979 addedExposure = exposure0.Factory(addedImage, exposure0.getWcs())