1 from __future__
import absolute_import, division, print_function
3 __all__ = [
"DynamicDetectionConfig",
"DynamicDetectionTask"]
10 from .detection
import SourceDetectionConfig, SourceDetectionTask
11 from .skyObjects
import SkyObjectsTask
19 """Configuration for DynamicDetectionTask""" 20 prelimThresholdFactor = Field(dtype=float, default=0.5,
21 doc=
"Fraction of the threshold to use for first pass (to find sky objects)")
22 skyObjects = ConfigurableField(target=SkyObjectsTask, doc=
"Generate sky objects")
26 """Detection of sources on an image with a dynamic threshold 28 We first detect sources using a lower threshold than normal (see config 29 parameter ``prelimThresholdFactor``) in order to identify good sky regions 30 (configurable ``skyObjects``). Then we perform forced PSF photometry on 31 those sky regions. Using those PSF flux measurements and estimated errors, 32 we set the threshold so that the stdev of the measurements matches the 33 median estimated error. 35 ConfigClass = DynamicDetectionConfig
36 _DefaultName =
"dynamicDetection" 41 Besides the usual initialisation of configurables, we also set up 42 the forced measurement which is deliberately not represented in 43 this Task's configuration parameters because we're using it as part 44 of the algorithm and we don't want to allow it to be modified. 46 SourceDetectionTask.__init__(self, *args, **kwargs)
47 self.makeSubtask(
"skyObjects")
50 config = ForcedMeasurementTask.ConfigClass()
51 config.plugins.names = [
'base_TransformedCentroid',
'base_PsfFlux']
53 for slot
in (
"shape",
"psfShape",
"apFlux",
"modelFlux",
"instFlux",
"calibFlux"):
54 setattr(config.slots, slot,
None)
55 config.copyColumns = {}
57 self.
skyMeasurement = ForcedMeasurementTask(config=config, name=
"skyMeasurement", parentTask=self,
61 """Calculate new threshold 63 This is the main functional addition to the vanilla 64 `SourceDetectionTask`. 66 We identify sky objects and perform forced PSF photometry on 67 them. Using those PSF flux measurements and estimated errors, 68 we set the threshold so that the stdev of the measurements 69 matches the median estimated error. 73 exposure : `lsst.afw.image.Exposure` 74 Exposure on which we're detecting sources. 76 RNG seed to use for finding sky objects. 77 sigma : `float`, optional 78 Gaussian sigma of smoothing kernel; if not provided, 79 will be deduced from the exposure's PSF. 84 Multiplication factor to be applied to the configured detection 88 fp = self.skyObjects.run(exposure.maskedImage.mask, seed)
90 skyFootprints.setFootprints(fp)
92 catalog = SourceCatalog(table)
93 table.preallocate(len(skyFootprints.getFootprints()))
94 skyFootprints.makeSources(catalog)
95 key = catalog.getCentroidKey()
96 for source
in catalog:
97 peaks = source.getFootprint().getPeaks()
98 assert len(peaks) == 1
99 source.set(key, peaks[0].getF())
100 source.updateCoord(exposure.getWcs())
103 self.
skyMeasurement.run(catalog, exposure, catalog, exposure.getWcs())
106 fluxes = catalog[
"base_PsfFlux_flux"]
107 lq, uq = np.percentile(fluxes, [25.0, 75.0])
108 stdev = 0.741*(uq - lq)
109 errors = catalog[
"base_PsfFlux_fluxSigma"]
110 median = np.median(errors)
113 def detectFootprints(self, exposure, doSmooth=True, sigma=None, clearMask=True, expId=None):
114 """Detect footprints with a dynamic threshold 116 This varies from the vanilla ``detectFootprints`` method because we 117 do detection twice: one with a low threshold so that we can find 118 sky uncontaminated by objects, then one more with the new calculated 123 exposure : `lsst.afw.image.Exposure` 124 Exposure to process; DETECTED{,_NEGATIVE} mask plane will be 126 doSmooth : `bool`, optional 127 If True, smooth the image before detection using a Gaussian 129 sigma : `float`, optional 130 Gaussian Sigma of PSF (pixels); used for smoothing and to grow 131 detections; if `None` then measure the sigma of the PSF of the 133 clearMask : `bool`, optional 134 Clear both DETECTED and DETECTED_NEGATIVE planes before running 136 expId : `int`, optional 137 Exposure identifier, used as a seed for the random number 138 generator. If absent, the seed will be the sum of the image. 140 Return Struct contents 141 ---------------------- 142 positive : `lsst.afw.detection.FootprintSet` 143 Positive polarity footprints (may be `None`) 144 negative : `lsst.afw.detection.FootprintSet` 145 Negative polarity footprints (may be `None`) 147 Number of footprints in positive or 0 if detection polarity was 150 Number of footprints in negative or 0 if detection polarity was 152 background : `lsst.afw.math.BackgroundMI` 153 Re-estimated background. `None` if 154 ``reEstimateBackground==False``. 156 Multiplication factor applied to the configured detection 159 maskedImage = exposure.maskedImage
162 self.clearMask(maskedImage.mask)
164 oldDetected = maskedImage.mask.array & maskedImage.mask.getPlaneBitMask([
"DETECTED",
165 "DETECTED_NEGATIVE"])
167 with self.tempWideBackgroundContext(exposure):
170 psf = self.getPsf(exposure, sigma=sigma)
171 convolveResults = self.convolveImage(maskedImage, psf, doSmooth=doSmooth)
172 middle = convolveResults.middle
173 sigma = convolveResults.sigma
175 prelim = self.applyThreshold(middle, maskedImage.getBBox(), self.config.prelimThresholdFactor)
176 self.finalizeFootprints(maskedImage.mask, prelim, sigma, self.config.prelimThresholdFactor)
180 seed = (expId
if expId
is not None else int(maskedImage.image.array.sum())) % (2**31 - 1)
182 self.log.info(
"Modifying configured detection threshold by factor %f to %f",
183 factor, factor*self.config.thresholdValue)
186 self.clearMask(maskedImage.mask)
188 maskedImage.mask.array |= oldDetected
191 results = self.applyThreshold(middle, maskedImage.getBBox(), factor)
192 results.prelim = prelim
193 if self.config.doTempLocalBackground:
194 self.applyTempLocalBackground(exposure, middle, results)
195 self.finalizeFootprints(maskedImage.mask, results, sigma, factor)
197 if self.config.reEstimateBackground:
198 self.reEstimateBackground(maskedImage, prelim)
200 self.clearUnwantedResults(maskedImage.mask, results)
201 self.display(exposure, results, middle)
def detectFootprints(self, exposure, doSmooth=True, sigma=None, clearMask=True, expId=None)
def calculateThreshold(self, exposure, seed, sigma=None)
def __init__(self, args, kwargs)