#
# LSST Data Management System
# Copyright 2008, 2009, 2010 LSST Corporation.
#
# This product includes software developed by the
# LSST Project (http://www.lsst.org/).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the LSST License Statement and
# the GNU General Public License along with this program. If not,
# see <http://www.lsstcorp.org/LegalNotices/>.
#
import unittest
import numpy as np
import lsst.utils.tests
import lsst.afw.image as afwImage
import lsst.afw.math as afwMath
from lsst.pipe.tasks.matchBackgrounds import MatchBackgroundsTask
class MatchBackgroundsTestCase(unittest.TestCase):
"""Background Matching"""
def setUp(self):
np.random.seed(1)
# Make a few test images here
# 1) full coverage (plain vanilla image) has mean = 50counts
self.vanilla = afwImage.ExposureF(600, 600)
im = self.vanilla.getMaskedImage().getImage()
afwMath.randomGaussianImage(im, afwMath.Random('MT19937', 1))
im += 50
self.vanilla.getMaskedImage().getVariance().set(1.0)
# 2) has chip gap and mean = 10 counts
self.chipGap = afwImage.ExposureF(600, 600)
im = self.chipGap.getMaskedImage().getImage()
afwMath.randomGaussianImage(im, afwMath.Random('MT19937', 2))
im += 10
im.getArray()[:, 200:300] = np.nan # simulate 100pix chip gap
self.chipGap.getMaskedImage().getVariance().set(1.0)
# 3) has low coverage and mean = 20 counts
self.lowCover = afwImage.ExposureF(600, 600)
im = self.lowCover.getMaskedImage().getImage()
afwMath.randomGaussianImage(im, afwMath.Random('MT19937', 3))
im += 20
self.lowCover.getMaskedImage().getImage().getArray()[:, 200:] = np.nan
self.lowCover.getMaskedImage().getVariance().set(1.0)
# make a matchBackgrounds object
self.matcher = MatchBackgroundsTask()
self.matcher.config.usePolynomial = True
self.matcher.binSize = 64
self.matcher.debugDataIdString = 'Test Visit'
self.sctrl = afwMath.StatisticsControl()
self.sctrl.setNanSafe(True)
self.sctrl.setAndMask(afwImage.Mask.getPlaneBitMask(["NO_DATA", "DETECTED", "DETECTED_NEGATIVE",
"SAT", "BAD", "INTRP", "CR"]))
def tearDown(self):
self.vanilla = None
self.lowCover = None
self.chipGap = None
self.matcher = None
self.sctrl = None
def checkAccuracy(self, refExp, sciExp):
"""Check for accurate background matching in the matchBackgrounds Method.
To be called by tests expecting successful matching.
"""
struct = self.matcher.matchBackgrounds(refExp, sciExp)
resultExp = sciExp
MSE = struct.matchedMSE
diffImVar = struct.diffImVar
resultStats = afwMath.makeStatistics(resultExp.getMaskedImage(),
afwMath.MEAN | afwMath.VARIANCE,
self.sctrl)
resultMean, _ = resultStats.getResult(afwMath.MEAN)
resultVar, _ = resultStats.getResult(afwMath.VARIANCE)
refStats = afwMath.makeStatistics(
refExp.getMaskedImage(), afwMath.MEAN | afwMath.VARIANCE, self.sctrl)
refMean, _ = refStats.getResult(afwMath.MEAN)
# print "refMean %.03f, resultMean %.03f, resultVar %.03f"%(refMean, resultMean, resultVar)
self.assertAlmostEqual(refMean, resultMean, delta=resultVar) # very loose test.
# If MSE is within 1% of the variance of the difference image: SUCCESS
self.assertLess(MSE, diffImVar * 1.01)
# -=-=-=-=-=-=Test Polynomial Fit (Approximate class)-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
def testConfig(self):
"""Test checks on the configuration.
1) Should throw ValueError if binsize is > size of image
2) Need Order + 1 points to fit. Should throw ValueError if Order > # of grid points
"""
for size in range(601, 1000, 100):
self.matcher.config.binSize = size
self.assertRaises(ValueError, self.matcher.matchBackgrounds, self.chipGap, self.vanilla)
# for image 600x600 and binsize 256 = 3x3 grid for fitting. order 3,4,5...should fail
self.matcher.config.binSize = 256
for order in range(3, 8):
self.matcher.config.order = order
self.assertRaises(ValueError, self.matcher.matchBackgrounds, self.chipGap, self.vanilla)
for size, order in [(600, 0), (300, 1), (200, 2), (100, 5)]:
self.matcher.config.binSize = size
self.matcher.config.order = order
self.checkAccuracy(self.chipGap, self.vanilla)
def testInputParams(self):
"""Test throws RuntimeError when dimensions don't match."""
self.matcher.config.binSize = 128
self.matcher.config.order = 2
# make image with wronge size
wrongSize = afwImage.ExposureF(500, 500)
wrongSize.getMaskedImage().getImage().set(1.0)
wrongSize.getMaskedImage().getVariance().set(1.0)
self.assertRaises(RuntimeError, self.matcher.matchBackgrounds, self.chipGap, wrongSize)
def testVanillaApproximate(self):
"""Test basic matching scenario with .Approximate."""
self.matcher.config.binSize = 128
self.matcher.config.order = 4
self.checkAccuracy(self.chipGap, self.vanilla)
def testRampApproximate(self):
"""Test basic matching of a linear gradient with Approximate."""
self.matcher.config.binSize = 64
testExp = afwImage.ExposureF(self.vanilla, True)
testIm = testExp.getMaskedImage().getImage()
afwMath.randomGaussianImage(testIm, afwMath.Random())
nx, ny = testExp.getDimensions()
dzdx, dzdy, z0 = 1, 2, 0.0
for x in range(nx):
for y in range(ny):
z = testIm[x, y, afwImage.LOCAL]
testIm[x, y, afwImage.LOCAL] = z + dzdx * x + dzdy * y + z0
self.checkAccuracy(testExp, self.vanilla)
def testLowCoverThrowExpectionApproximate(self):
"""Test low coverage with .config.undersampleStyle = THROW_EXCEPTION.
Confirm throws ValueError with Approximate.
"""
self.matcher.config.binSize = 64
self.matcher.config.order = 8
self.matcher.config.undersampleStyle = "THROW_EXCEPTION"
self.assertRaises(ValueError, self.matcher.matchBackgrounds, self.chipGap, self.lowCover)
def testLowCoverIncreaseSampleApproximate(self):
"""Test low coverage with .config.undersampleStyle = INCREASE_NXNYSAMPLE.
Confirm successful matching with .Approximate.
"""
self.matcher.config.binSize = 128
self.matcher.config.order = 4
self.matcher.config.undersampleStyle = "INCREASE_NXNYSAMPLE"
self.checkAccuracy(self.chipGap, self.lowCover)
def testLowCoverReduceInterpOrderApproximate(self):
"""Test low coverage with .config.undersampleStyle = REDUCE_INTERP_ORDER.
Confirm successful matching with .Approximate.
"""
self.matcher.config.binSize = 64
self.matcher.config.order = 8
self.matcher.config.undersampleStyle = "REDUCE_INTERP_ORDER"
self.checkAccuracy(self.chipGap, self.lowCover)
def testMasksApproximate(self):
"""Test that masks are ignored in matching backgrounds: .Approximate."""
testExp = afwImage.ExposureF(self.chipGap, True)
im = testExp.getMaskedImage().getImage()
im += 10
mask = testExp.getMaskedImage().getMask()
satbit = mask.getPlaneBitMask('SAT')
for i in range(0, 200, 20):
mask[5, i, afwImage.LOCAL] = satbit
im[5, i, afwImage.LOCAL] = 65000
self.matcher.config.binSize = 128
self.matcher.config.order = 4
self.checkAccuracy(self.chipGap, testExp)
def testWeightsByInvError(self):
"""Test that bins with high std.dev. and low count are weighted less in fit."""
testExp = afwImage.ExposureF(self.chipGap, True)
testIm = testExp.getMaskedImage().getImage()
self.matcher.config.binSize = 60
self.matcher.config.order = 4
for x in range(0, 50):
for y in range(0, 50):
if np.random.rand(1)[0] < 0.6:
testIm[x, y, afwImage.LOCAL] = np.nan
else:
testIm[x, y, afwImage.LOCAL] = np.random.rand()*1000
self.matcher.matchBackgrounds(self.vanilla, testExp)
resultExp = testExp
resultArr = resultExp.getMaskedImage().getImage().getArray()[60:, 60:]
resultMean = np.mean(resultArr[np.where(~np.isnan(resultArr))])
resultVar = np.std(resultArr[np.where(~np.isnan(resultArr))])**2
self.assertAlmostEqual(50, resultMean, delta=resultVar) # very loose test.
def testSameImageApproximate(self):
"""Test able to match identical images: .Approximate."""
vanillaTwin = afwImage.ExposureF(self.vanilla, True)
self.matcher.config.binSize = 128
self.matcher.config.order = 4
self.checkAccuracy(self.vanilla, vanillaTwin)
# -=-=-=-=-=-=-=-=-=Background Interp (Splines) -=-=-=-=-=-=-=-=-
def testVanillaBackground(self):
"""Test basic matching scenario with .Background."""
self.matcher.config.usePolynomial = False
self.matcher.config.binSize = 128
self.checkAccuracy(self.chipGap, self.vanilla)
def testRampBackground(self):
"""Test basic matching of a linear gradient with .Background."""
self.matcher.config.usePolynomial = False
self.matcher.config.binSize = 64
testExp = afwImage.ExposureF(self.vanilla, True)
testIm = testExp.getMaskedImage().getImage()
afwMath.randomGaussianImage(testIm, afwMath.Random())
nx, ny = testExp.getDimensions()
dzdx, dzdy, z0 = 1, 2, 0.0
for x in range(nx):
for y in range(ny):
z = testIm[x, y, afwImage.LOCAL]
testIm[x, y, afwImage.LOCAL] = z + dzdx * x + dzdy * y + z0
self.checkAccuracy(testExp, self.vanilla)
def testUndersampleBackgroundPasses(self):
"""Test undersample style (REDUCE_INTERP_ORDER): .Background.
INCREASE_NXNYSAMPLE no longer supported by .Background because .Backgrounds's are
defined by their nx and ny grid.
"""
self.matcher.config.usePolynomial = False
self.matcher.config.binSize = 256
self.matcher.config.undersampleStyle = "REDUCE_INTERP_ORDER"
self.checkAccuracy(self.chipGap, self.vanilla)
self.matcher.config.undersampleStyle = "INCREASE_NXNYSAMPLE"
self.assertRaises(RuntimeError, self.matcher.matchBackgrounds, self.chipGap, self.vanilla)
def testSameImageBackground(self):
"""Test able to match identical images with .Background."""
self.matcher.config.usePolynomial = False
self.matcher.config.binSize = 256
self.checkAccuracy(self.vanilla, self.vanilla)
def testMasksBackground(self):
"""Test masks ignored in matching backgrounds with .Background."""
self.matcher.config.usePolynomial = False
self.matcher.config.binSize = 256
testExp = afwImage.ExposureF(self.chipGap, True)
im = testExp.getMaskedImage().getImage()
im += 10
mask = testExp.getMaskedImage().getMask()
satbit = mask.getPlaneBitMask('SAT')
for i in range(0, 200, 20):
mask[5, i, afwImage.LOCAL] = satbit
im[5, i, afwImage.LOCAL] = 65000
self.checkAccuracy(self.chipGap, testExp)
def testChipGapHorizontalBackground(self):
""" Test able to match image with horizontal chip gap (row of nans) with .Background"""
self.matcher.config.usePolynomial = False
self.matcher.config.binSize = 64
chipGapHorizontal = afwImage.ExposureF(600, 600)
im = chipGapHorizontal.getMaskedImage().getImage()
afwMath.randomGaussianImage(im, afwMath.Random())
im += 10
im.getArray()[200:300, :] = np.nan # simulate 100pix chip gap horizontal
chipGapHorizontal.getMaskedImage().getVariance().set(1.0)
self.checkAccuracy(self.vanilla, chipGapHorizontal)
def testChipGapVerticalBackground(self):
""" Test able to match images with vertical chip gaps (column of nans) wider than bin size"""
self.matcher.config.usePolynomial = False
self.matcher.config.binSize = 64
self.checkAccuracy(self.chipGap, self.vanilla)
def testLowCoverBackground(self):
""" Test able to match images that do not cover the whole patch"""
self.matcher.config.usePolynomial = False
self.matcher.config.binSize = 64
self.checkAccuracy(self.vanilla, self.lowCover)
def setup_module(module):
lsst.utils.tests.init()
class MatchMemoryTestCase(lsst.utils.tests.MemoryTestCase):
pass
316 ↛ 317line 316 didn't jump to line 317, because the condition on line 316 was never trueif __name__ == "__main__":
lsst.utils.tests.init()
unittest.main()
|