Coverage for tests/test_matchBackgrounds.py: 15%
199 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-13 17:20 -0700
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-13 17:20 -0700
1#
2# LSST Data Management System
3# Copyright 2008, 2009, 2010 LSST Corporation.
4#
5# This product includes software developed by the
6# LSST Project (http://www.lsst.org/).
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the LSST License Statement and
19# the GNU General Public License along with this program. If not,
20# see <http://www.lsstcorp.org/LegalNotices/>.
21#
23import unittest
25import numpy as np
27import lsst.utils.tests
28import lsst.afw.image as afwImage
29import lsst.afw.math as afwMath
30from lsst.pipe.tasks.matchBackgrounds import MatchBackgroundsTask
33class MatchBackgroundsTestCase(unittest.TestCase):
35 """Background Matching"""
37 def setUp(self):
38 np.random.seed(1)
40 # Make a few test images here
41 # 1) full coverage (plain vanilla image) has mean = 50counts
42 self.vanilla = afwImage.ExposureF(600, 600)
43 im = self.vanilla.getMaskedImage().getImage()
44 afwMath.randomGaussianImage(im, afwMath.Random('MT19937', 1))
45 im += 50
46 self.vanilla.getMaskedImage().getVariance().set(1.0)
48 # 2) has chip gap and mean = 10 counts
49 self.chipGap = afwImage.ExposureF(600, 600)
50 im = self.chipGap.getMaskedImage().getImage()
51 afwMath.randomGaussianImage(im, afwMath.Random('MT19937', 2))
52 im += 10
53 im.getArray()[:, 200:300] = np.nan # simulate 100pix chip gap
54 self.chipGap.getMaskedImage().getVariance().set(1.0)
56 # 3) has low coverage and mean = 20 counts
57 self.lowCover = afwImage.ExposureF(600, 600)
58 im = self.lowCover.getMaskedImage().getImage()
59 afwMath.randomGaussianImage(im, afwMath.Random('MT19937', 3))
60 im += 20
61 self.lowCover.getMaskedImage().getImage().getArray()[:, 200:] = np.nan
62 self.lowCover.getMaskedImage().getVariance().set(1.0)
64 # make a matchBackgrounds object
65 self.matcher = MatchBackgroundsTask()
66 self.matcher.config.usePolynomial = True
67 self.matcher.binSize = 64
68 self.matcher.debugDataIdString = 'Test Visit'
70 self.sctrl = afwMath.StatisticsControl()
71 self.sctrl.setNanSafe(True)
72 self.sctrl.setAndMask(afwImage.Mask.getPlaneBitMask(["NO_DATA", "DETECTED", "DETECTED_NEGATIVE",
73 "SAT", "BAD", "INTRP", "CR"]))
75 def tearDown(self):
76 self.vanilla = None
77 self.lowCover = None
78 self.chipGap = None
79 self.matcher = None
80 self.sctrl = None
82 def checkAccuracy(self, refExp, sciExp):
83 """Check for accurate background matching in the matchBackgrounds Method.
85 To be called by tests expecting successful matching.
86 """
87 struct = self.matcher.matchBackgrounds(refExp, sciExp)
88 resultExp = sciExp
89 MSE = struct.matchedMSE
90 diffImVar = struct.diffImVar
92 resultStats = afwMath.makeStatistics(resultExp.getMaskedImage(),
93 afwMath.MEAN | afwMath.VARIANCE,
94 self.sctrl)
95 resultMean, _ = resultStats.getResult(afwMath.MEAN)
96 resultVar, _ = resultStats.getResult(afwMath.VARIANCE)
97 refStats = afwMath.makeStatistics(
98 refExp.getMaskedImage(), afwMath.MEAN | afwMath.VARIANCE, self.sctrl)
99 refMean, _ = refStats.getResult(afwMath.MEAN)
100 self.assertAlmostEqual(refMean, resultMean, delta=resultVar) # very loose test.
101 # If MSE is within 1% of the variance of the difference image: SUCCESS
102 self.assertLess(MSE, diffImVar * 1.01)
104 # -=-=-=-=-=-=Test Polynomial Fit (Approximate class)-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
106 def testConfig(self):
107 """Test checks on the configuration.
109 1) Should throw ValueError if binsize is > size of image
110 2) Need Order + 1 points to fit. Should throw ValueError if Order > # of grid points
111 """
112 for size in range(601, 1000, 100):
113 self.matcher.config.binSize = size
114 self.assertRaises(ValueError, self.matcher.matchBackgrounds, self.chipGap, self.vanilla)
116 # for image 600x600 and binsize 256 = 3x3 grid for fitting. order 3,4,5...should fail
117 self.matcher.config.binSize = 256
118 for order in range(3, 8):
119 self.matcher.config.order = order
120 self.assertRaises(ValueError, self.matcher.matchBackgrounds, self.chipGap, self.vanilla)
122 for size, order in [(600, 0), (300, 1), (200, 2), (100, 5)]:
123 self.matcher.config.binSize = size
124 self.matcher.config.order = order
125 self.checkAccuracy(self.chipGap, self.vanilla)
127 def testInputParams(self):
128 """Test throws RuntimeError when dimensions don't match."""
129 self.matcher.config.binSize = 128
130 self.matcher.config.order = 2
131 # make image with wronge size
132 wrongSize = afwImage.ExposureF(500, 500)
133 wrongSize.getMaskedImage().getImage().set(1.0)
134 wrongSize.getMaskedImage().getVariance().set(1.0)
135 self.assertRaises(RuntimeError, self.matcher.matchBackgrounds, self.chipGap, wrongSize)
137 def testVanillaApproximate(self):
138 """Test basic matching scenario with .Approximate."""
139 self.matcher.config.binSize = 128
140 self.matcher.config.order = 4
141 self.checkAccuracy(self.chipGap, self.vanilla)
143 def testRampApproximate(self):
144 """Test basic matching of a linear gradient with Approximate."""
145 self.matcher.config.binSize = 64
146 testExp = afwImage.ExposureF(self.vanilla, True)
147 testIm = testExp.getMaskedImage().getImage()
148 afwMath.randomGaussianImage(testIm, afwMath.Random())
149 nx, ny = testExp.getDimensions()
150 dzdx, dzdy, z0 = 1, 2, 0.0
151 for x in range(nx):
152 for y in range(ny):
153 z = testIm[x, y, afwImage.LOCAL]
154 testIm[x, y, afwImage.LOCAL] = z + dzdx * x + dzdy * y + z0
155 self.checkAccuracy(testExp, self.vanilla)
157 def testLowCoverThrowExpectionApproximate(self):
158 """Test low coverage with .config.undersampleStyle = THROW_EXCEPTION.
159 Confirm throws ValueError with Approximate.
160 """
161 self.matcher.config.binSize = 64
162 self.matcher.config.order = 8
163 self.matcher.config.undersampleStyle = "THROW_EXCEPTION"
164 self.assertRaises(ValueError, self.matcher.matchBackgrounds, self.chipGap, self.lowCover)
166 def testLowCoverIncreaseSampleApproximate(self):
167 """Test low coverage with .config.undersampleStyle = INCREASE_NXNYSAMPLE.
168 Confirm successful matching with .Approximate.
169 """
170 self.matcher.config.binSize = 128
171 self.matcher.config.order = 4
172 self.matcher.config.undersampleStyle = "INCREASE_NXNYSAMPLE"
173 self.checkAccuracy(self.chipGap, self.lowCover)
175 def testLowCoverReduceInterpOrderApproximate(self):
176 """Test low coverage with .config.undersampleStyle = REDUCE_INTERP_ORDER.
177 Confirm successful matching with .Approximate.
178 """
179 self.matcher.config.binSize = 64
180 self.matcher.config.order = 8
181 self.matcher.config.undersampleStyle = "REDUCE_INTERP_ORDER"
182 self.checkAccuracy(self.chipGap, self.lowCover)
184 def testMasksApproximate(self):
185 """Test that masks are ignored in matching backgrounds: .Approximate."""
186 testExp = afwImage.ExposureF(self.chipGap, True)
187 im = testExp.getMaskedImage().getImage()
188 im += 10
189 mask = testExp.getMaskedImage().getMask()
190 satbit = mask.getPlaneBitMask('SAT')
191 for i in range(0, 200, 20):
192 mask[5, i, afwImage.LOCAL] = satbit
193 im[5, i, afwImage.LOCAL] = 65000
194 self.matcher.config.binSize = 128
195 self.matcher.config.order = 4
196 self.checkAccuracy(self.chipGap, testExp)
198 def testWeightsByInvError(self):
199 """Test that bins with high std.dev. and low count are weighted less in fit."""
200 testExp = afwImage.ExposureF(self.chipGap, True)
201 testIm = testExp.getMaskedImage().getImage()
202 self.matcher.config.binSize = 60
203 self.matcher.config.order = 4
204 for x in range(0, 50):
205 for y in range(0, 50):
206 if np.random.rand(1)[0] < 0.6:
207 testIm[x, y, afwImage.LOCAL] = np.nan
208 else:
209 testIm[x, y, afwImage.LOCAL] = np.random.rand()*1000
211 self.matcher.matchBackgrounds(self.vanilla, testExp)
212 resultExp = testExp
213 resultArr = resultExp.getMaskedImage().getImage().getArray()[60:, 60:]
214 resultMean = np.mean(resultArr[np.where(~np.isnan(resultArr))])
215 resultVar = np.std(resultArr[np.where(~np.isnan(resultArr))])**2
216 self.assertAlmostEqual(50, resultMean, delta=resultVar) # very loose test.
218 def testSameImageApproximate(self):
219 """Test able to match identical images: .Approximate."""
220 vanillaTwin = afwImage.ExposureF(self.vanilla, True)
221 self.matcher.config.binSize = 128
222 self.matcher.config.order = 4
223 self.checkAccuracy(self.vanilla, vanillaTwin)
225 # -=-=-=-=-=-=-=-=-=Background Interp (Splines) -=-=-=-=-=-=-=-=-
227 def testVanillaBackground(self):
228 """Test basic matching scenario with .Background."""
229 self.matcher.config.usePolynomial = False
230 self.matcher.config.binSize = 128
231 self.checkAccuracy(self.chipGap, self.vanilla)
233 def testRampBackground(self):
234 """Test basic matching of a linear gradient with .Background."""
235 self.matcher.config.usePolynomial = False
236 self.matcher.config.binSize = 64
237 testExp = afwImage.ExposureF(self.vanilla, True)
238 testIm = testExp.getMaskedImage().getImage()
239 afwMath.randomGaussianImage(testIm, afwMath.Random())
240 nx, ny = testExp.getDimensions()
241 dzdx, dzdy, z0 = 1, 2, 0.0
242 for x in range(nx):
243 for y in range(ny):
244 z = testIm[x, y, afwImage.LOCAL]
245 testIm[x, y, afwImage.LOCAL] = z + dzdx * x + dzdy * y + z0
246 self.checkAccuracy(testExp, self.vanilla)
248 def testUndersampleBackgroundPasses(self):
249 """Test undersample style (REDUCE_INTERP_ORDER): .Background.
251 INCREASE_NXNYSAMPLE no longer supported by .Background because .Backgrounds's are
252 defined by their nx and ny grid.
253 """
254 self.matcher.config.usePolynomial = False
255 self.matcher.config.binSize = 256
256 self.matcher.config.undersampleStyle = "REDUCE_INTERP_ORDER"
257 self.checkAccuracy(self.chipGap, self.vanilla)
259 self.matcher.config.undersampleStyle = "INCREASE_NXNYSAMPLE"
260 self.assertRaises(RuntimeError, self.matcher.matchBackgrounds, self.chipGap, self.vanilla)
262 def testSameImageBackground(self):
263 """Test able to match identical images with .Background."""
264 self.matcher.config.usePolynomial = False
265 self.matcher.config.binSize = 256
266 self.checkAccuracy(self.vanilla, self.vanilla)
268 def testMasksBackground(self):
269 """Test masks ignored in matching backgrounds with .Background."""
270 self.matcher.config.usePolynomial = False
271 self.matcher.config.binSize = 256
272 testExp = afwImage.ExposureF(self.chipGap, True)
273 im = testExp.getMaskedImage().getImage()
274 im += 10
275 mask = testExp.getMaskedImage().getMask()
276 satbit = mask.getPlaneBitMask('SAT')
277 for i in range(0, 200, 20):
278 mask[5, i, afwImage.LOCAL] = satbit
279 im[5, i, afwImage.LOCAL] = 65000
280 self.checkAccuracy(self.chipGap, testExp)
282 def testChipGapHorizontalBackground(self):
283 """ Test able to match image with horizontal chip gap (row of nans) with .Background"""
284 self.matcher.config.usePolynomial = False
285 self.matcher.config.binSize = 64
286 chipGapHorizontal = afwImage.ExposureF(600, 600)
287 im = chipGapHorizontal.getMaskedImage().getImage()
288 afwMath.randomGaussianImage(im, afwMath.Random())
289 im += 10
290 im.getArray()[200:300, :] = np.nan # simulate 100pix chip gap horizontal
291 chipGapHorizontal.getMaskedImage().getVariance().set(1.0)
292 self.checkAccuracy(self.vanilla, chipGapHorizontal)
294 def testChipGapVerticalBackground(self):
295 """ Test able to match images with vertical chip gaps (column of nans) wider than bin size"""
296 self.matcher.config.usePolynomial = False
297 self.matcher.config.binSize = 64
298 self.checkAccuracy(self.chipGap, self.vanilla)
300 def testLowCoverBackground(self):
301 """ Test able to match images that do not cover the whole patch"""
302 self.matcher.config.usePolynomial = False
303 self.matcher.config.binSize = 64
304 self.checkAccuracy(self.vanilla, self.lowCover)
307def setup_module(module):
308 lsst.utils.tests.init()
311class MatchMemoryTestCase(lsst.utils.tests.MemoryTestCase):
312 pass
315if __name__ == "__main__": 315 ↛ 316line 315 didn't jump to line 316, because the condition on line 315 was never true
316 lsst.utils.tests.init()
317 unittest.main()