102 def run(self, table, exposure, initialThreshold=None, initialThresholdMultiplier=2.0):
103 """Perform detection with an adaptive threshold detection scheme
104 conditioned to maximize the likelihood of a successful PSF model fit
105 for any given "scene".
107 In particular, we'd like to be able to handle different scenes, from
108 sparsely populated ones through very crowded ones, and possibly high
109 fill-factor nebulosity along the way, with a single pipeline (and set
110 of configs). This requires some flexibility in setting the detection
111 thresholds in order to detect enough sources suitable for PSF modeling
112 (e.g. crowded fields require higher thresholds to ensure the detections
113 don't end up overlapping into a single or very small number of blended
116 We first detect sources using the default threshold and multiplier.
117 Then, cycling through a series of criteria based on the DETECTED mask
118 planes (number of footprints, number of peaks, number of isolated
119 footprints, number of peaks-per-footrpint, etc.) conditioned to identify
120 a "Goldilocks Zone" where we have enough isolated peaks from which to
121 measure the PSF, we iterate while adjusting the detection thresholds
122 in the appropriate direction until all criteria are met (or the maximum
123 number of iterations is reached).
127 table : `lsst.afw.table.SourceTable`
128 Table object that will be used to create the SourceCatalog.
129 exposure : `lsst.afw.image.Exposure`
130 Exposure to process; DETECTED mask plane will be set in-place.
131 initialThreshold : `float`, optional
132 Initial threshold for detection of PSF sources.
133 initialThresholdMultiplier : `float`, optional
134 Initial threshold for detection of PSF sources.
138 results : `lsst.pipe.base.Struct`
139 The adaptive threshold detection results as a struct with
143 Results of the final round of detection as a struch with
147 Detected sources on the exposure
148 (`lsst.afw.table.SourceCatalog`).
150 Positive polarity footprints
151 (`lsst.afw.detection.FootprintSet` or `None`).
153 Negative polarity footprints
154 (`lsst.afw.detection.FootprintSet` or `None`).
156 Number of footprints in positive or 0 if detection polarity was
159 Number of footprints in negative or 0 if detection polarity was
162 Always `None`; provided for compatibility with
163 `SourceDetectionTask`.
165 Multiplication factor applied to the configured detection
166 threshold. (`float`).
168 The final threshold value used to the configure the final round
169 of detection (`float`).
170 ``includeThresholdMultiplier``
171 The final multiplication factor applied to the configured detection
172 threshold. (`float`).
175 if exposure.filter
is not None:
176 if exposure.filter.hasBandLabel():
177 band = exposure.filter.bandLabel
178 if band
in self.config.maxNumPeakPerBand:
179 maxNumPeak = self.config.maxNumPeakPerBand[band]
181 maxNumPeak = self.config.maxNumPeakPerBand[
"fallback"]
184 thresholdFactor = 1.0
185 if initialThreshold
is None:
186 maxSn = float(np.nanmax(exposure.image.array/np.sqrt(exposure.variance.array)))
187 adaptiveDetThreshold = min(maxSn, 5.0)
189 adaptiveDetThreshold = initialThreshold
191 adaptiveDetectionConfig.thresholdValue = adaptiveDetThreshold
192 adaptiveDetectionConfig.includeThresholdMultiplier = initialThresholdMultiplier
193 adaptiveDetectionConfig.reEstimateBackground =
False
194 adaptiveDetectionConfig.doTempWideBackground =
True
195 adaptiveDetectionConfig.tempWideBackground.binSize = 512
196 adaptiveDetectionConfig.thresholdPolarity =
"both"
197 self.log.info(
"Using adaptive detection with thresholdValue = %.2f and multiplier = %.1f",
198 adaptiveDetectionConfig.thresholdValue,
199 adaptiveDetectionConfig.includeThresholdMultiplier)
202 maxNumNegFactor = 1.0
204 for nAdaptiveDetIter
in range(1, self.config.maxAdaptiveDetIter + 1):
205 detRes = adaptiveDetectionTask.run(table=table, exposure=exposure, doSmooth=
True)
206 sourceCat = detRes.sources
207 nFootprint = len(sourceCat)
209 nPosPeak = detRes.numPosPeaks
210 nNegPeak = detRes.numNegPeaks
211 maxNumNegPeak = max(15, int(0.025*nPosPeak))*maxNumNegFactor
214 maxNumPeakPerSrcMax = 0.2*maxNumPeak
215 for src
in sourceCat:
216 nPeakSrc = len(src.getFootprint().getPeaks())
220 nPeakPerSrcMax = max(nPeakPerSrcMax, nPeakSrc)
222 fractionIsolated = nIsolated/nFootprint
223 avgPeakPerFoot = nPeak/nFootprint
225 fractionIsolated = float(
"nan")
226 avgPeakPerFoot = float(
"nan")
227 self.log.info(
"In adaptive detection iter %d: nFootprints = %d, nPosPeak = %d (max is %d), "
228 "nNegPeak = %d (max is %d), nPeak/nFoot = %.1f (max is %.1f), "
229 "nPeakPerkSrcMax = %d (max is %d), nIsolated = %d, fractionIsolated = %.2f",
230 nAdaptiveDetIter, nFootprint, nPeak, maxNumPeak, nNegPeak, maxNumNegPeak,
231 avgPeakPerFoot, self.config.maxPeakToFootRatio, nPeakPerSrcMax,
232 maxNumPeakPerSrcMax, nIsolated, fractionIsolated)
233 if (nIsolated > self.config.sufficientIsolated
234 and fractionIsolated > self.config.sufficientFractionIsolated
235 and (nAdaptiveDetIter > 1
or self.config.maxAdaptiveDetIter == 1)):
236 if ((nIsolated > 5.0*self.config.sufficientIsolated
and nPeak < 2.5*maxNumPeak
237 and nNegPeak < 100.0*maxNumNegPeak)
238 or (nNegPeak < 5.0*maxNumNegPeak
and nPeak < 1.2*maxNumPeak)):
239 self.log.info(
"Sufficient isolated footprints (%d > %d) and fraction of isolated "
240 "footprints (%.2f > %.2f) for PSF modeling. Exiting adaptive detection "
242 nIsolated, self.config.sufficientIsolated, fractionIsolated,
243 self.config.sufficientFractionIsolated, nAdaptiveDetIter)
246 if nFootprint == 0
or nPosPeak == 0:
247 thresholdFactor = 0.25
248 maxNumNegFactor *= 10
249 self.log.warning(
"Adaptive threshold increase went too far (nFootprint = 0). "
250 "Decreasing threshold to %.2f and rerunning.",
251 thresholdFactor*adaptiveDetectionConfig.thresholdValue)
252 adaptiveDetectionConfig.thresholdValue = (
253 thresholdFactor*adaptiveDetectionConfig.thresholdValue)
257 if ((nPeak/nFootprint > self.config.maxPeakToFootRatio
and nIsolated < self.config.minIsolated)
258 or nNegPeak > maxNumNegPeak):
259 if nNegPeak > 2*maxNumNegPeak:
260 thresholdFactor = 1.5
262 thresholdFactor = 1.25
263 thresholdFactor *= adaptiveDetectionConfig.includeThresholdMultiplier
264 adaptiveDetectionConfig.includeThresholdMultiplier = 1.0
265 self.log.warning(
"Adaptive detection iter %d: catalog had nPeak/nFootprint = "
266 "%.1f (max is %.1f) and %d negative peaks (max is %d). "
267 "Increasing threshold to %.2f and setting multiplier "
268 "to %.1f and rerunning.",
269 nAdaptiveDetIter, nPeak/nFootprint, self.config.maxPeakToFootRatio,
270 nNegPeak, maxNumNegPeak,
271 thresholdFactor*adaptiveDetectionConfig.thresholdValue,
272 adaptiveDetectionConfig.includeThresholdMultiplier)
273 adaptiveDetectionConfig.thresholdValue = (
274 thresholdFactor*adaptiveDetectionConfig.thresholdValue)
278 if (nPeak > maxNumPeak
or nPeakPerSrcMax > maxNumPeakPerSrcMax
279 or nFootprint <= self.config.minFootprint):
280 if nPeak > maxNumPeak
or nPeakPerSrcMax > 0.25*maxNumPeak:
281 if nAdaptiveDetIter < 0.5*self.config.maxAdaptiveDetIter:
282 if nPeak > 3*maxNumPeak
or nPeakPerSrcMax > maxNumPeak:
283 thresholdFactor = 1.7
284 elif nPeak > 2*maxNumPeak
or nPeakPerSrcMax > 0.5*maxNumPeak:
285 thresholdFactor = 1.4
287 thresholdFactor = 1.2
289 thresholdFactor = 1.2
290 thresholdFactor *= adaptiveDetectionConfig.includeThresholdMultiplier
291 newThresholdMultiplier = max(1.0, 0.5*adaptiveDetectionConfig.includeThresholdMultiplier)
292 adaptiveDetectionConfig.includeThresholdMultiplier = newThresholdMultiplier
293 adaptiveDetectionConfig.thresholdValue = (
294 thresholdFactor*adaptiveDetectionConfig.thresholdValue)
295 self.log.warning(
"Adaptive detection iter %d catalog had nPeak = %d (max = %d) "
296 "and nPeakPerSrcMax = %d (max = %d). Increasing threshold to %.2f "
297 "and setting multiplier to %.1f and rerunning.",
298 nAdaptiveDetIter, nPeak, maxNumPeak, nPeakPerSrcMax, maxNumPeakPerSrcMax,
299 adaptiveDetectionConfig.thresholdValue,
300 adaptiveDetectionConfig.includeThresholdMultiplier)
304 if nFootprint <= self.config.minFootprint:
305 maxNumNegFactor *= 10
306 thresholdFactor = min(0.85, 0.4*np.log10(10*(nFootprint + 1)))
307 self.log.warning(
"Adaptive detection iter %d catalog had only %d footprints. "
308 "Lowering threshold to %.2f and increasing the allowance for "
309 "negative detections and rerunning.",
310 nAdaptiveDetIter, nFootprint,
311 thresholdFactor*adaptiveDetectionConfig.thresholdValue)
313 adaptiveDetectionConfig.thresholdValue = (
314 thresholdFactor*adaptiveDetectionConfig.thresholdValue
320 adaptiveDetectionConfig.thresholdPolarity =
"positive"
322 self.log.info(
"Perfomring final round of detection with threshold %.2f and multiplier %.1f",
323 adaptiveDetectionConfig.thresholdValue,
324 adaptiveDetectionConfig.includeThresholdMultiplier)
325 detRes = adaptiveDetectionTask.run(table=table, exposure=exposure, doSmooth=
True)
328 thresholdValue=adaptiveDetectionConfig.thresholdValue,
329 includeThresholdMultiplier=adaptiveDetectionConfig.includeThresholdMultiplier,