Coverage for tests/test_fringes.py: 16%
146 statements
« prev ^ index » next coverage.py v7.5.0, created at 2024-05-03 03:19 -0700
« prev ^ index » next coverage.py v7.5.0, created at 2024-05-03 03:19 -0700
1# This file is part of ip_isr.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <https://www.gnu.org/licenses/>.
22import unittest
24import numpy as np
26import lsst.utils.tests
27import lsst.afw.math as afwMath
28import lsst.afw.image as afwImage
30from lsst.ip.isr.fringe import FringeTask
32import lsst.ip.isr.isrMock as isrMock
34try:
35 display
36except NameError:
37 display = False
38else:
39 import lsst.afw.display as afwDisplay
40 afwDisplay.setDefaultMaskTransparency(75)
43def createFringe(width, height, xFreq, xOffset, yFreq, yOffset):
44 """Create a fringe frame.
46 Parameters
47 ----------
48 width, height : `int`
49 Size of image.
50 xFreq, yFreq : `float`
51 Frequency of sinusoids in x and y.
52 xOffset, yOffset : `float`
53 Phase of sinusoids in x and y.
55 Returns
56 -------
57 exp : `lsst.afw.image.ExposureF`
58 Fringe frame.
59 """
60 image = afwImage.ImageF(width, height)
61 array = image.getArray()
62 x, y = np.indices(array.shape)
63 array[x, y] = np.sin(xFreq*x + xOffset) + np.sin(yFreq*y + yOffset)
64 mi = afwImage.makeMaskedImage(image)
65 exp = afwImage.makeExposure(mi)
66 exp.setFilter(afwImage.FilterLabel(band='y', physical='FILTER'))
67 return exp
70class FringeTestCase(lsst.utils.tests.TestCase):
71 """Tests of the FringeTask.
72 """
73 def setUp(self):
74 self.size = 512
75 self.config = FringeTask.ConfigClass()
76 self.config.filters = ['FILTER', 'y']
77 self.config.num = 5000
78 self.config.small = 1
79 self.config.large = 128
80 self.config.pedestal = False
81 self.config.iterations = 10
83 def checkFringe(self, task, exp, fringes, stddevMax):
84 """Run fringe subtraction and verify.
86 Parameters
87 ----------
88 task : `lsst.ip.isr.fringe.FringeTask`
89 Task to run.
90 exp : `lsst.afw.image.ExposureF`
91 Science exposure.
92 fringes : `list` of `lsst.afw.image.ExposureF`
93 Data reference that will provide the fringes.
94 stddevMax : `float`
95 Maximum allowable standard deviation.
96 """
97 if display:
98 frame = 0
99 afwDisplay.Display(frame=frame).mtv(exp, title=self._testMethodName + ": Science exposure")
100 frame += 1
101 if not isinstance(fringes, list):
102 fringe = [fringes]
103 else:
104 fringe = fringes
105 for i, f in enumerate(fringe):
106 afwDisplay.Display(frame=frame).mtv(f, title=self._testMethodName
107 + ": Fringe frame %d" % (i + 1))
108 frame += 1
110 task.run(exp, fringes)
112 mi = exp.getMaskedImage()
114 if display:
115 afwDisplay.Display(frame=frame).mtv(exp, title=self._testMethodName + ": Subtracted")
116 frame += 1
118 mi -= afwMath.makeStatistics(mi, afwMath.MEAN).getValue()
119 self.assertLess(afwMath.makeStatistics(mi, afwMath.STDEV).getValue(), stddevMax)
121 def testSingle(self, pedestal=0.0, stddevMax=1.0e-4):
122 """Test subtraction of a single fringe frame.
124 Parameters
125 ----------
126 pedestal : `float`, optional
127 Pedestal to add into fringe frame
128 stddevMax : `float`, optional
129 Maximum allowable standard deviation.
130 """
131 xFreq = np.pi/10.0
132 xOffset = 1.0
133 yFreq = np.pi/15.0
134 yOffset = 0.5
135 scale = 1.0
136 fringe = createFringe(self.size, self.size, xFreq, xOffset, yFreq, yOffset)
137 fMi = fringe.getMaskedImage()
138 fMi += pedestal
139 exp = createFringe(self.size, self.size, xFreq, xOffset, yFreq, yOffset)
140 eMi = exp.getMaskedImage()
141 eMi *= scale
143 task = FringeTask(name="fringe", config=self.config)
144 self.checkFringe(task, exp, fringe, stddevMax)
146 def testBad(self, bad="BAD"):
147 """Test fringe subtraction with bad inputs.
149 Parameters
150 ----------
151 bad : `str`, optional
152 Mask plane to use.
153 """
154 xFreq = np.pi/10.0
155 xOffset = 1.0
156 yFreq = np.pi/15.0
157 yOffset = 0.5
158 fringe = createFringe(self.size, self.size, xFreq, xOffset, yFreq, yOffset)
159 exp = createFringe(self.size, self.size, xFreq, xOffset, yFreq, yOffset)
161 # This is a bad CCD: entirely masked
162 exp.maskedImage.image.set(0.0)
163 mask = exp.maskedImage.mask
164 mask.set(mask.getPlaneBitMask(bad))
166 self.config.stats.badMaskPlanes = [bad]
167 task = FringeTask(name="fringe", config=self.config)
168 task.run(exp, fringe)
169 self.assertFloatsEqual(exp.maskedImage.image.array, 0.0)
171 def testPedestal(self):
172 """Test subtraction of a fringe frame with a pedestal.
173 """
174 self.config.pedestal = True
175 self.testSingle(pedestal=10000.0, stddevMax=1.0e-3) # Not sure why this produces worse sttdev
176 self.testMultiple(pedestal=10000.0)
178 def testMultiple(self, pedestal=0.0):
179 """Test subtraction of multiple fringe frames
181 Paramters
182 ---------
183 pedestal : `float`, optional
184 Pedestal to add into fringe frame.
185 """
186 xFreqList = [0.1, 0.13, 0.06]
187 xOffsetList = [0.0, 0.1, 0.2]
188 yFreqList = [0.09, 0.12, 0.07]
189 yOffsetList = [0.3, 0.2, 0.1]
190 fringeList = [createFringe(self.size, self.size, xFreq, xOffset, yFreq, yOffset)
191 for xFreq, xOffset, yFreq, yOffset in
192 zip(xFreqList, xOffsetList, yFreqList, yOffsetList)]
194 for fringe in fringeList:
195 fMi = fringe.getMaskedImage()
196 fMi += pedestal
197 # Generate science frame
198 scales = [0.33, 0.33, 0.33]
199 image = afwImage.ImageF(self.size, self.size)
200 image.set(0)
201 for s, f in zip(scales, fringeList):
202 image.scaledPlus(s, f.getMaskedImage().getImage())
203 mi = afwImage.makeMaskedImage(image)
204 exp = afwImage.makeExposure(mi)
205 exp.setFilter(afwImage.FilterLabel(physical='FILTER'))
207 task = FringeTask(name="multiFringe", config=self.config)
208 self.checkFringe(task, exp, fringeList, stddevMax=1.0e-2)
210 def testRun(self, pedestal=0.0, stddevMax=1.0e-4):
211 """Test the .run method for complete test converage.
213 Paramters
214 ---------
215 pedestal : `float`, optional
216 Pedestal to add into fringe frame.
217 stddevMax : `float`, optional
218 Maximum allowable standard deviation.
219 """
220 xFreq = np.pi/10.0
221 xOffset = 1.0
222 yFreq = np.pi/15.0
223 yOffset = 0.5
224 scale = 1.0
225 fringe = createFringe(self.size, self.size, xFreq, xOffset, yFreq, yOffset)
226 fMi = fringe.getMaskedImage()
227 fMi += pedestal
228 exp = createFringe(self.size, self.size, xFreq, xOffset, yFreq, yOffset)
229 eMi = exp.getMaskedImage()
230 eMi *= scale
232 task = FringeTask(name="fringe", config=self.config)
233 task.run(exp, fringe)
235 mi = exp.getMaskedImage()
236 mi -= afwMath.makeStatistics(mi, afwMath.MEAN).getValue()
237 self.assertLess(afwMath.makeStatistics(mi, afwMath.STDEV).getValue(), stddevMax)
239 def test_multiFringes(self):
240 """Test that multi-fringe results are handled correctly by the task.
241 """
242 self.config.large = 16
243 task = FringeTask(name="multiFringeMock", config=self.config)
245 config = isrMock.IsrMockConfig()
246 config.fringeScale = [750.0, 240.0, 220.0]
247 config.fringeX0 = [100.0, 150.0, 200.0]
248 config.fringeY0 = [0.0, 200.0, 0.0]
250 fringeContainer = isrMock.MockFringeContainer(config=config)
251 exp = fringeContainer.get("raw")
252 exp.setFilter(afwImage.FilterLabel(physical='FILTER'))
253 medianBefore = np.nanmedian(exp.getImage().getArray())
254 fringe = fringeContainer.get("fringe")
255 fringes = task.loadFringes(fringe)
257 solution, rms = task.run(exp, **fringes.getDict())
258 medianAfter = np.nanmedian(exp.getImage().getArray())
259 stdAfter = np.nanstd(exp.getImage().getArray())
261 self.assertLess(medianAfter, medianBefore)
262 self.assertFloatsAlmostEqual(medianAfter, 3000.925, atol=1e-4)
263 self.assertFloatsAlmostEqual(stdAfter, 3549.9885, atol=1e-4)
265 deviation = np.abs(solution - config.fringeScale)
266 self.assertTrue(np.all(deviation / rms < 1.0))
269class MemoryTester(lsst.utils.tests.MemoryTestCase):
270 pass
273def setup_module(module):
274 lsst.utils.tests.init()
277if __name__ == "__main__": 277 ↛ 278line 277 didn't jump to line 278, because the condition on line 277 was never true
278 lsst.utils.tests.init()
279 unittest.main()