231 background=None):
232 """Detect footprints with a dynamic threshold
233
234 This varies from the vanilla ``detectFootprints`` method because we
235 do detection three times: first with a high threshold to detect
236 "bright" (both positive and negative, the latter to identify very
237 over-subtracted regions) sources for which we grow the DETECTED and
238 DETECTED_NEGATIVE masks significantly to account for wings. Second,
239 with a low threshold to mask all non-empty regions of the image. These
240 two masks are combined and used to identify regions of sky
241 uncontaminated by objects. A final round of detection is then done
242 with the new calculated threshold.
243
244 Parameters
245 ----------
246 exposure : `lsst.afw.image.Exposure`
247 Exposure to process; DETECTED{,_NEGATIVE} mask plane will be
248 set in-place.
249 doSmooth : `bool`, optional
250 If True, smooth the image before detection using a Gaussian
251 of width ``sigma``.
252 sigma : `float`, optional
253 Gaussian Sigma of PSF (pixels); used for smoothing and to grow
254 detections; if `None` then measure the sigma of the PSF of the
255 ``exposure``.
256 clearMask : `bool`, optional
257 Clear both DETECTED and DETECTED_NEGATIVE planes before running
258 detection.
259 expId : `int`, optional
260 Exposure identifier, used as a seed for the random number
261 generator. If absent, the seed will be the sum of the image.
262 background : `lsst.afw.math.BackgroundList`, optional
263 Background that was already subtracted from the exposure; will be
264 modified in-place if ``reEstimateBackground=True``.
265
266 Returns
267 -------
268 resutls : `lsst.pipe.base.Struct`
269 The results `~lsst.pipe.base.Struct` contains:
270
271 ``positive``
272 Positive polarity footprints.
273 (`lsst.afw.detection.FootprintSet` or `None`)
274 ``negative``
275 Negative polarity footprints.
276 (`lsst.afw.detection.FootprintSet` or `None`)
277 ``numPos``
278 Number of footprints in positive or 0 if detection polarity was
279 negative. (`int`)
280 ``numNeg``
281 Number of footprints in negative or 0 if detection polarity was
282 positive. (`int`)
283 ``background``
284 Re-estimated background. `None` or the input ``background``
285 if ``reEstimateBackground==False``.
286 (`lsst.afw.math.BackgroundList`)
287 ``factor``
288 Multiplication factor applied to the configured detection
289 threshold. (`float`)
290 ``prelim``
291 Results from preliminary detection pass.
292 (`lsst.pipe.base.Struct`)
293 """
294 maskedImage = exposure.maskedImage
295
296 if clearMask:
297 self.clearMask(maskedImage.mask)
298 else:
299 oldDetected = maskedImage.mask.array & maskedImage.mask.getPlaneBitMask(["DETECTED",
300 "DETECTED_NEGATIVE"])
301 nPix = maskedImage.mask.array.size
303 nGoodPix = np.sum(maskedImage.mask.array & badPixelMask == 0)
304 self.log.info("Number of good data pixels (i.e. not NO_DATA or BAD): {} ({:.1f}% of total)".
305 format(nGoodPix, 100*nGoodPix/nPix))
306
307 with self.tempWideBackgroundContext(exposure):
308
309
310
311 psf = self.getPsf(exposure, sigma=sigma)
312 convolveResults = self.convolveImage(maskedImage, psf, doSmooth=doSmooth)
313
314 if self.config.doBrightPrelimDetection:
315 brightDetectedMask = self._computeBrightDetectionMask(maskedImage, convolveResults)
316
317 middle = convolveResults.middle
318 sigma = convolveResults.sigma
319 prelim = self.applyThreshold(
320 middle, maskedImage.getBBox(), factor=self.config.prelimThresholdFactor,
321 factorNeg=self.config.prelimNegMultiplier*self.config.prelimThresholdFactor
322 )
323 self.finalizeFootprints(
324 maskedImage.mask, prelim, sigma, factor=self.config.prelimThresholdFactor,
325 factorNeg=self.config.prelimNegMultiplier*self.config.prelimThresholdFactor
326 )
327 if self.config.doBrightPrelimDetection:
328
329
330 maskedImage.mask.array |= brightDetectedMask
331
332
333
334 seed = (expId if expId is not None else int(maskedImage.image.array.sum())) % (2**31 - 1)
335 threshResults = self.calculateThreshold(exposure, seed, sigma=sigma)
336 factor = threshResults.multiplicative
337 self.log.info("Modifying configured detection threshold by factor %f to %f",
338 factor, factor*self.config.thresholdValue)
339
340
341 self.clearMask(maskedImage.mask)
342 if not clearMask:
343 maskedImage.mask.array |= oldDetected
344
345
346 results = self.applyThreshold(middle, maskedImage.getBBox(), factor)
347 results.prelim = prelim
349 if self.config.doTempLocalBackground:
350 self.applyTempLocalBackground(exposure, middle, results)
351 self.finalizeFootprints(maskedImage.mask, results, sigma, factor=factor)
352
353 self.clearUnwantedResults(maskedImage.mask, results)
354
355 if self.config.reEstimateBackground:
356 self.reEstimateBackground(maskedImage, results.background)
357
358 self.display(exposure, results, middle)
359
360 if self.config.doBackgroundTweak:
361
362
363
364
365
366
367
368
369 originalMask = maskedImage.mask.array.copy()
370 try:
371 self.clearMask(exposure.mask)
372 convolveResults = self.convolveImage(maskedImage, psf, doSmooth=doSmooth)
373 tweakDetResults = self.applyThreshold(convolveResults.middle, maskedImage.getBBox(), factor)
374 self.finalizeFootprints(maskedImage.mask, tweakDetResults, sigma, factor=factor)
375 bgLevel = self.calculateThreshold(exposure, seed, sigma=sigma, minFractionSourcesFactor=0.5,
376 isBgTweak=True).additive
377 finally:
378 maskedImage.mask.array[:] = originalMask
379 self.tweakBackground(exposure, bgLevel, results.background)
380
381 return results
382