Skip to content

Commit e1553d0

Browse files
Merge pull request #383 from lsst/tickets/DM-48920
DM-48920: Handle NaNs in variance values when creating templates
2 parents 7c8978a + 40dcd7f commit e1553d0

File tree

2 files changed

+49
-15
lines changed

2 files changed

+49
-15
lines changed

python/lsst/ip/diffim/getTemplate.py

+18-12
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,8 @@ def _makeExposureCatalog(self, exposures, dataIds):
375375

376376
return images, catalog, totalBox
377377

378-
def _merge(self, maskedImages, bbox, wcs):
378+
@staticmethod
379+
def _merge(maskedImages, bbox, wcs):
379380
"""Merge the images that came from one tract into one larger image,
380381
ignoring NaN pixels and non-finite variance pixels from individual
381382
exposures.
@@ -398,8 +399,11 @@ def _merge(self, maskedImages, bbox, wcs):
398399
merged = afwImage.ExposureF(bbox, wcs)
399400
weights = afwImage.ImageF(bbox)
400401
for maskedImage in maskedImages:
401-
weight = afwImage.ImageF(maskedImage.variance.array**(-0.5))
402-
bad = np.isnan(maskedImage.image.array) | ~np.isfinite(maskedImage.variance.array)
402+
# Catch both zero-value and NaN variance plane pixels
403+
good = maskedImage.variance.array > 0
404+
weight = afwImage.ImageF(maskedImage.getBBox())
405+
weight.array[good] = maskedImage.variance.array[good]**(-0.5)
406+
bad = np.isnan(maskedImage.image.array) | ~good
403407
# Note that modifying the patch MaskedImage in place is fine;
404408
# we're throwing it away at the end anyway.
405409
maskedImage.image.array[bad] = 0.0
@@ -412,16 +416,18 @@ def _merge(self, maskedImages, bbox, wcs):
412416
maskedImage.image *= weight
413417
maskedImage.variance *= weight
414418
merged.maskedImage[maskedImage.getBBox()] += maskedImage
415-
# Clear the NaNs to ensure that areas missing from this input are
416-
# masked with NO_DATA after the loop.
417-
weight.array[np.isnan(weight.array)] = 0
418419
weights[maskedImage.getBBox()] += weight
419-
# Cannot use `merged.maskedImage /= weights` because that operator
420-
# divides the variance by the weight twice; in this case `weights` are
421-
# the exact values we want to scale by.
422-
merged.image /= weights
423-
merged.variance /= weights
424-
merged.mask.array |= merged.mask.getPlaneBitMask("NO_DATA") * (weights.array == 0)
420+
421+
inverseWeights = np.zeros_like(weights.array)
422+
good = weights.array > 0
423+
inverseWeights[good] = 1/weights.array[good]
424+
425+
# Cannot use `merged.maskedImage *= inverseWeights` because that
426+
# operator divides the variance by the weight twice; in this case
427+
# `inverseWeights` are the exact values we want to scale by.
428+
merged.image.array *= inverseWeights
429+
merged.variance.array *= inverseWeights
430+
merged.mask.array |= merged.mask.getPlaneBitMask("NO_DATA") * (inverseWeights == 0)
425431

426432
return merged
427433

tests/test_getTemplate.py

+31-3
Original file line numberDiff line numberDiff line change
@@ -282,9 +282,37 @@ def testMissingPatches(self):
282282
dataIds=self.dataIds,
283283
physical_filter="a_test")
284284
no_data = (result.template.mask.array & result.template.mask.getPlaneBitMask("NO_DATA")) != 0
285-
self.assertTrue(all(np.isnan(result.template.image.array[no_data])))
286-
self.assertTrue(all(np.isnan(result.template.variance.array[no_data])))
287-
self.assertEqual(no_data.sum(), 21548)
285+
self.assertTrue(np.isfinite(result.template.image.array).all())
286+
self.assertTrue(np.isfinite(result.template.variance.array).all())
287+
self.assertEqual(no_data.sum(), 20990)
288+
289+
@lsst.utils.tests.methodParameters(
290+
box=[
291+
lsst.geom.Box2I(lsst.geom.Point2I(0, 0), lsst.geom.Point2I(180, 180)),
292+
lsst.geom.Box2I(lsst.geom.Point2I(200, 200), lsst.geom.Point2I(600, 600)),
293+
],
294+
nInput=[8, 16],
295+
)
296+
def testNanInputs(self, box=None, nInput=None):
297+
"""Test that the template has finite values when some of the input pixels have NaN as variance."""
298+
for tract, patchCoadds in self.patches.items():
299+
for patchCoadd in patchCoadds:
300+
bbox = lsst.geom.Box2I()
301+
bbox.include(lsst.geom.Point2I(patchCoadd.getBBox().getCenter()))
302+
bbox.grow(3)
303+
patchCoadd.variance[bbox].array *= np.nan
304+
305+
box = lsst.geom.Box2I(lsst.geom.Point2I(200, 200), lsst.geom.Point2I(600, 600))
306+
task = lsst.ip.diffim.GetTemplateTask()
307+
result = task.run(coaddExposures=self.patches,
308+
bbox=lsst.geom.Box2I(box),
309+
wcs=self.exposure.wcs,
310+
dataIds=self.dataIds,
311+
physical_filter="a_test")
312+
self._checkMetadata(result.template, task.config, box, self.exposure.wcs, 16)
313+
# We just check that the pixel values are all finite. We cannot check that pixel values
314+
# in the template are closer to the original anymore.
315+
self.assertTrue(np.isfinite(result.template.image.array).all())
288316

289317

290318
def setup_module(module):

0 commit comments

Comments
 (0)