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"])
169 psf = self.getPsf(exposure, sigma=sigma)
170 convolveResults = self.convolveImage(maskedImage, psf, doSmooth=doSmooth)
171 middle = convolveResults.middle
172 sigma = convolveResults.sigma
174 prelim = self.applyThreshold(middle, maskedImage.getBBox(), self.config.prelimThresholdFactor)
175 self.finalizeFootprints(maskedImage.mask, prelim, sigma, self.config.prelimThresholdFactor)
179 seed = (expId
if expId
is not None else int(maskedImage.image.array.sum())) % (2**31 - 1)
181 self.log.info(
"Modifying configured detection threshold by factor %f to %f",
182 factor, factor*self.config.thresholdValue)
185 self.clearMask(maskedImage.mask)
187 maskedImage.mask.array |= oldDetected
190 results = self.applyThreshold(middle, maskedImage.getBBox(), factor)
191 results.prelim = prelim
192 if self.config.doTempLocalBackground:
193 self.applyTempLocalBackground(exposure, middle, results)
194 self.finalizeFootprints(maskedImage.mask, results, sigma, factor)
196 if self.config.reEstimateBackground:
197 self.reEstimateBackground(maskedImage, prelim)
199 self.clearUnwantedResults(maskedImage.mask, results)
200 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)