From d1d4d4218566373a12aa6b0b91e583b03082033b Mon Sep 17 00:00:00 2001 From: Ian Sullivan Date: Wed, 11 Jun 2025 21:50:26 -0700 Subject: [PATCH 1/2] Use the template mask plane also to reject kernel candidates --- python/lsst/ip/diffim/makeKernel.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/python/lsst/ip/diffim/makeKernel.py b/python/lsst/ip/diffim/makeKernel.py index 357219039..fee5b1183 100644 --- a/python/lsst/ip/diffim/makeKernel.py +++ b/python/lsst/ip/diffim/makeKernel.py @@ -335,6 +335,11 @@ def makeCandidateList(self, convolved, reference, kernelSize, if (reference.subset(bbox).mask.array & bitmask).any(): good[i] = False continue + + # Reject footprints with any bad mask bits set. + if (convolved.subset(bbox).mask.array & bitmask).any(): + good[i] = False + continue candidates = candidateList[good].copy(deep=True) self.log.info("Selected %d / %d sources as kernel candidates.", good.sum(), len(candidateList)) From afb128c9d923c72941eb5ba152aa57a6d2f4ae89 Mon Sep 17 00:00:00 2001 From: Ian Sullivan Date: Wed, 11 Jun 2025 21:52:06 -0700 Subject: [PATCH 2/2] Remove duplicate mask check A better check is now run in makeKernel.makeCandidateList --- python/lsst/ip/diffim/subtractImages.py | 42 ------------------------- tests/test_detectAndMeasure.py | 7 ++--- tests/utils.py | 41 ++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 46 deletions(-) diff --git a/python/lsst/ip/diffim/subtractImages.py b/python/lsst/ip/diffim/subtractImages.py index 0cff6f092..f9d916cd7 100644 --- a/python/lsst/ip/diffim/subtractImages.py +++ b/python/lsst/ip/diffim/subtractImages.py @@ -858,7 +858,6 @@ def _sourceSelector(self, sources, mask): selected = self.sourceSelector.selectSources(sources).selected nInitialSelected = np.count_nonzero(selected) - selected *= self._checkMask(mask, sources, self.config.excludeMaskPlanes) nSelected = np.count_nonzero(selected) self.log.info("Rejecting %i candidate sources: an excluded template mask plane is set.", nInitialSelected - nSelected) @@ -885,47 +884,6 @@ def _sourceSelector(self, sources, mask): return selectSources - @staticmethod - def _checkMask(mask, sources, excludeMaskPlanes, checkAdjacent=True): - """Exclude sources that are located on or adjacent to masked pixels. - - Parameters - ---------- - mask : `lsst.afw.image.Mask` - The image mask plane to use to reject sources - based on the location of their centroid on the ccd. - sources : `lsst.afw.table.SourceCatalog` - The source catalog to evaluate. - excludeMaskPlanes : `list` of `str` - List of the names of the mask planes to exclude. - - Returns - ------- - flags : `numpy.ndarray` of `bool` - Array indicating whether each source in the catalog should be - kept (True) or rejected (False) based on the value of the - mask plane at its location. - """ - setExcludeMaskPlanes = [ - maskPlane for maskPlane in excludeMaskPlanes if maskPlane in mask.getMaskPlaneDict() - ] - - excludePixelMask = mask.getPlaneBitMask(setExcludeMaskPlanes) - - xv = (np.rint(sources.getX() - mask.getX0())).astype(int) - yv = (np.rint(sources.getY() - mask.getY0())).astype(int) - - flags = np.ones(len(sources), dtype=bool) - if checkAdjacent: - pixRange = (0, -1, 1) - else: - pixRange = (0,) - for j in pixRange: - for i in pixRange: - mv = mask.array[yv + j, xv + i] - flags *= np.bitwise_and(mv, excludePixelMask) == 0 - return flags - def _prepareInputs(self, template, science, visitSummary=None): """Perform preparatory calculations common to all Alard&Lupton Tasks. diff --git a/tests/test_detectAndMeasure.py b/tests/test_detectAndMeasure.py index 86b8c315d..449ef9825 100644 --- a/tests/test_detectAndMeasure.py +++ b/tests/test_detectAndMeasure.py @@ -32,7 +32,7 @@ import lsst.utils.tests import lsst.meas.base.tests -from utils import makeTestImage +from utils import makeTestImage, checkMask class DetectAndMeasureTestBase: @@ -505,7 +505,6 @@ def test_exclude_mask_detections(self): science, sources = makeTestImage(noiseLevel=noiseLevel, noiseSeed=6, **kwargs) matchedTemplate, _ = makeTestImage(noiseLevel=noiseLevel/4, noiseSeed=7, **kwargs) - _checkMask = subtractImages.AlardLuptonSubtractTask._checkMask # Configure the detection Task detectionTask = self._setup_detection() excludeMaskPlanes = detectionTask.config.detection.excludeMaskPlanes @@ -530,7 +529,7 @@ def _detection_wrapper(setFlags=True): difference[srcBbox].mask.array |= lsst.afw.image.Mask.getPlaneBitMask(badMask) output = detectionTask.run(science, matchedTemplate, difference) refIds = [] - goodSrcFlags = _checkMask(difference.mask, transientSources, excludeMaskPlanes) + goodSrcFlags = checkMask(difference.mask, transientSources, excludeMaskPlanes) if setFlags: self.assertEqual(np.sum(~goodSrcFlags), nBad) self.assertFalse(hasattr(output, "diaSources")) @@ -965,7 +964,7 @@ def _detection_wrapper(setFlags=True): score = subtractTask._convolveExposure(difference, scienceKernel, subtractTask.convolutionControl) output = detectionTask.run(science, matchedTemplate, difference, score) refIds = [] - goodSrcFlags = subtractTask._checkMask(difference.mask, transientSources, excludeMaskPlanes) + goodSrcFlags = checkMask(difference.mask, transientSources, excludeMaskPlanes) if setFlags: self.assertEqual(np.sum(~goodSrcFlags), nBad) self.assertFalse(hasattr(output, "diaSources")) diff --git a/tests/utils.py b/tests/utils.py index 6ac08c791..f257a13e9 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1219,3 +1219,44 @@ def generate_data_id(*, data_id = DataCoordinate.standardize(record_id, universe=universe) return data_id.expanded(record) + + +def checkMask(mask, sources, excludeMaskPlanes, checkAdjacent=True): + """Exclude sources that are located on or adjacent to masked pixels. + + Parameters + ---------- + mask : `lsst.afw.image.Mask` + The image mask plane to use to reject sources + based on the location of their centroid on the ccd. + sources : `lsst.afw.table.SourceCatalog` + The source catalog to evaluate. + excludeMaskPlanes : `list` of `str` + List of the names of the mask planes to exclude. + + Returns + ------- + flags : `numpy.ndarray` of `bool` + Array indicating whether each source in the catalog should be + kept (True) or rejected (False) based on the value of the + mask plane at its location. + """ + setExcludeMaskPlanes = [ + maskPlane for maskPlane in excludeMaskPlanes if maskPlane in mask.getMaskPlaneDict() + ] + + excludePixelMask = mask.getPlaneBitMask(setExcludeMaskPlanes) + + xv = (np.rint(sources.getX() - mask.getX0())).astype(int) + yv = (np.rint(sources.getY() - mask.getY0())).astype(int) + + flags = np.ones(len(sources), dtype=bool) + if checkAdjacent: + pixRange = (0, -1, 1) + else: + pixRange = (0,) + for j in pixRange: + for i in pixRange: + mv = mask.array[yv + j, xv + i] + flags *= np.bitwise_and(mv, excludePixelMask) == 0 + return flags