Skip to content

Commit 46f3599

Browse files
committed
Add condition on number of peaks per footprint
If the detection mask ends up as a single, or few many-peaked footprints, then all subsequent processing will fall over. Impose a limit to the maximum number of peaks in a given footprint for the detection mask. If this condition is not met, iteravely increase the detection threshold until it does. This is meant to be a no-op for most "typical" scenes and observing conditions, but can help avoid a total downstream meltdown in certain cases (e.g. very crowded regions).
1 parent 27ca5a5 commit 46f3599

File tree

1 file changed

+59
-13
lines changed

1 file changed

+59
-13
lines changed

python/lsst/meas/algorithms/dynamicDetection.py

Lines changed: 59 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,15 @@ class DynamicDetectionConfig(SourceDetectionConfig):
105105
"suitable locations to lay down sky objects. To allow for best effort "
106106
"sky source placement, if True, this allows for a slight erosion of "
107107
"the detection masks.")
108+
maxPeakToFootRatio = Field(dtype=float, default=150.0,
109+
doc="Maximum ratio of peak per footprint in the detection mask. "
110+
"This is to help prevent single contiguous footprints that nothing "
111+
"can be done with (i.e. deblending will be skipped). If the current "
112+
"detection plane does not satisfy this constraint, the detection "
113+
"threshold is increased iteratively until it is. This behaviour is "
114+
"intended to be an effective no-op for most \"typical\" scenes/standard "
115+
"quality observations, but can avoid total meltdown in, e.g. very "
116+
"crowded regions.")
108117

109118
def setDefaults(self):
110119
SourceDetectionConfig.setDefaults(self)
@@ -424,22 +433,59 @@ def detectFootprints(self, exposure, doSmooth=True, sigma=None, clearMask=True,
424433
# seed needs to fit in a C++ 'int' so pybind doesn't choke on it
425434
seed = (expId if expId is not None else int(maskedImage.image.array.sum())) % (2**31 - 1)
426435
threshResults = self.calculateThreshold(exposure, seed, sigma=sigma)
427-
factor = threshResults.multiplicative
436+
minMultiplicative = 0.5
437+
if threshResults.multiplicative < minMultiplicative:
438+
self.log.warning("threshResults.multiplicative = %.2f is less than minimum value (%.2f). "
439+
"Setting to %.2f.", threshResults.multiplicative, minMultiplicative,
440+
minMultiplicative)
441+
factor = max(minMultiplicative, threshResults.multiplicative)
428442
self.log.info("Modifying configured detection threshold by factor %.2f to %.2f",
429443
factor, factor*self.config.thresholdValue)
430444

431-
# Blow away preliminary (low threshold) detection mask
432-
self.clearMask(maskedImage.mask)
433-
if not clearMask:
434-
maskedImage.mask.array |= oldDetected
435-
436-
# Rinse and repeat thresholding with new calculated threshold
437-
results = self.applyThreshold(middle, maskedImage.getBBox(), factor)
438-
results.prelim = prelim
439-
results.background = background if background is not None else lsst.afw.math.BackgroundList()
440-
if self.config.doTempLocalBackground:
441-
self.applyTempLocalBackground(exposure, middle, results)
442-
self.finalizeFootprints(maskedImage.mask, results, sigma, factor=factor)
445+
growOverride = None
446+
inFinalize = True
447+
while inFinalize:
448+
inFinalize = False
449+
# Blow away preliminary (low threshold) detection mask
450+
self.clearMask(maskedImage.mask)
451+
if not clearMask:
452+
maskedImage.mask.array |= oldDetected
453+
454+
# Rinse and repeat thresholding with new calculated threshold
455+
results = self.applyThreshold(middle, maskedImage.getBBox(), factor)
456+
results.prelim = prelim
457+
results.background = background if background is not None else lsst.afw.math.BackgroundList()
458+
if self.config.doTempLocalBackground:
459+
self.applyTempLocalBackground(exposure, middle, results)
460+
self.finalizeFootprints(maskedImage.mask, results, sigma, factor=factor,
461+
growOverride=growOverride)
462+
self.log.warning("nPeaks/nFootprint = %.2f (max is %.1f)",
463+
results.numPosPeaks/results.numPos,
464+
self.config.maxPeakToFootRatio)
465+
if results.numPosPeaks/results.numPos > self.config.maxPeakToFootRatio:
466+
if results.numPosPeaks/results.numPos > 3*self.config.maxPeakToFootRatio:
467+
factor *= 1.4
468+
else:
469+
factor *= 1.2
470+
if factor > 2.0:
471+
if growOverride is None:
472+
growOverride = 0.75*self.config.nSigmaToGrow
473+
else:
474+
growOverride *= 0.75
475+
self.log.warning("Decreasing nSigmaToGrow to %.2f", growOverride)
476+
if factor >= 5:
477+
self.log.warning("New theshold value would be > 5 times the initially requested "
478+
"one (%.2f > %.2f). Leaving inFinalize iteration without "
479+
"getting the number of peaks per footprint below %.1f",
480+
factor*self.config.thresholdValue, self.config.thresholdValue,
481+
self.config.maxPeakToFootRatio)
482+
inFinalize = False
483+
else:
484+
inFinalize = True
485+
self.log.warning("numPosPeaks/numPos (%d) > maxPeakPerFootprint (%.1f). "
486+
"Increasing threshold factor to %.2f and re-running,",
487+
results.numPosPeaks/results.numPos, self.config.maxPeakToFootRatio,
488+
factor)
443489

444490
self.clearUnwantedResults(maskedImage.mask, results)
445491

0 commit comments

Comments
 (0)