diff --git a/python/lsst/ip/diffim/detectAndMeasure.py b/python/lsst/ip/diffim/detectAndMeasure.py index 0057fd47..7a2157f3 100644 --- a/python/lsst/ip/diffim/detectAndMeasure.py +++ b/python/lsst/ip/diffim/detectAndMeasure.py @@ -861,6 +861,9 @@ def processResults(self, science, matchedTemplate, difference, sources, idFactor initialDiaSources["is_negative"] = initialDiaSources["merge_footprint_negative"] & \ ~initialDiaSources["merge_footprint_positive"] self.log.info("Merging detections into %d sources", len(initialDiaSources)) + # All positive peaks were added before any negative peaks, so + # re-order the peaks in each footprint by significance. + self._reorderPeaksBySignificance(initialDiaSources) else: initialDiaSources = sources @@ -934,6 +937,24 @@ def processResults(self, science, matchedTemplate, difference, sources, idFactor ) return measurementResults + def _reorderPeaksBySignificance(self, diaSources): + """Sort each merged source's footprint peaks by significance. + + Parameters + ---------- + diaSources : `lsst.afw.table.SourceCatalog` + Merged sources whose footprint peaks are re-ordered in place. + """ + for source in diaSources: + peaks = source.getFootprint().getPeaks() + if len(peaks) < 2 or "significance" not in peaks.schema.getNames(): + continue + ordered = sorted(peaks, key=lambda peak: peak["significance"], reverse=True) + newPeaks = afwDetection.PeakCatalog(peaks.table) + newPeaks.extend(ordered) + peaks.clear() + source.getFootprint().setPeakCatalog(newPeaks) + def _deblend(self, difference, positiveFootprints, negativeFootprints): """Deblend the positive and negative footprints and return a catalog containing just the children, and the deblended footprints. diff --git a/tests/test_detectAndMeasure.py b/tests/test_detectAndMeasure.py index ba1ba772..58315d35 100644 --- a/tests/test_detectAndMeasure.py +++ b/tests/test_detectAndMeasure.py @@ -25,8 +25,10 @@ from unittest import mock import requests +import lsst.afw.detection as afwDetection import lsst.afw.geom as afwGeom import lsst.afw.image as afwImage +import lsst.afw.table as afwTable import lsst.afw.math as afwMath import lsst.geom from lsst.ip.diffim import detectAndMeasure, subtractImages @@ -1500,6 +1502,41 @@ def testMergeFootprints(self): # handled. self.assertEqual((~result.diaSources["is_negative"]).sum(), 3) + def testReorderPeaksBySignificance(self): + """A merged footprint whose most significant peak is negative should + have that peak first after re-ordering, even though the merge lists + positive peaks before negative ones. + """ + config = detectAndMeasure.DetectAndMeasureTask.ConfigClass() + task = detectAndMeasure.DetectAndMeasureTask(config=config) + + # Peak schema with a significance field, as SourceDetectionTask + # produces for pixel_stdev thresholds. + mapper = afwTable.SchemaMapper(afwDetection.PeakTable.makeMinimalSchema()) + mapper.addMinimalSchema(afwDetection.PeakTable.makeMinimalSchema()) + mapper.addOutputField("significance", type=float, doc="") + peakSchema = mapper.getOutputSchema() + + diaSources = afwTable.SourceCatalog(task.schema) + source = diaSources.addNew() + spans = afwGeom.SpanSet(geom.Box2I(geom.Point2I(0, 0), geom.Extent2I(10, 10))) + footprint = afwDetection.Footprint(spans, peakSchema) + # Merge order is [positive, negative]: a weak positive peak first, + # then a more significant negative peak. + footprint.addPeak(1, 1, 30.0) + footprint.peaks[-1]["significance"] = 5.0 + footprint.addPeak(2, 2, -500.0) + footprint.peaks[-1]["significance"] = 40.0 + source.setFootprint(footprint) + + task._reorderPeaksBySignificance(diaSources) + + peaks = source.getFootprint().peaks + self.assertEqual(peaks[0].getPeakValue(), -500.0) + self.assertEqual(peaks[0]["significance"], 40.0) + self.assertEqual(peaks[1].getPeakValue(), 30.0) + self.assertEqual(peaks[1]["significance"], 5.0) + def makeVisitInfo(id=1): """Return a non-NaN visitInfo."""