Coverage for tests/test_fringes.py: 17%
161 statements
« prev ^ index » next coverage.py v7.1.0, created at 2023-02-05 18:17 -0800
« prev ^ index » next coverage.py v7.1.0, created at 2023-02-05 18:17 -0800
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
29import lsst.pipe.base as pipeBase
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)
43class FringeDataRef(object):
44 """Quacks like a ButlerDataRef, so we can provide an in-memory fringe frame.
45 """
46 def __init__(self, fringe):
47 self.fringe = fringe
48 self.dataId = {'test': True}
50 def get(self, name="fringe", immediate=False):
51 if name == "fringe":
52 return self.fringe
53 if name == "ccdExposureId":
54 return 1000
57def createFringe(width, height, xFreq, xOffset, yFreq, yOffset):
58 """Create a fringe frame.
60 Parameters
61 ----------
62 width, height : `int`
63 Size of image.
64 xFreq, yFreq : `float`
65 Frequency of sinusoids in x and y.
66 xOffset, yOffset : `float`
67 Phase of sinusoids in x and y.
69 Returns
70 -------
71 exp : `lsst.afw.image.ExposureF`
72 Fringe frame.
73 """
74 image = afwImage.ImageF(width, height)
75 array = image.getArray()
76 x, y = np.indices(array.shape)
77 array[x, y] = np.sin(xFreq*x + xOffset) + np.sin(yFreq*y + yOffset)
78 mi = afwImage.makeMaskedImage(image)
79 exp = afwImage.makeExposure(mi)
80 exp.setFilterLabel(afwImage.FilterLabel(band='y', physical='FILTER'))
81 return exp
84class FringeTestCase(lsst.utils.tests.TestCase):
85 """Tests of the FringeTask.
86 """
87 def setUp(self):
88 self.size = 512
89 self.config = FringeTask.ConfigClass()
90 self.config.filters = ['FILTER', 'y']
91 self.config.num = 5000
92 self.config.small = 1
93 self.config.large = 128
94 self.config.pedestal = False
95 self.config.iterations = 10
97 def checkFringe(self, task, exp, fringes, stddevMax):
98 """Run fringe subtraction and verify.
100 Parameters
101 ----------
102 task : `lsst.ip.isr.fringe.FringeTask`
103 Task to run.
104 exp : `lsst.afw.image.ExposureF`
105 Science exposure.
106 fringes : `list` of `lsst.afw.image.ExposureF`
107 Data reference that will provide the fringes.
108 stddevMax : `float`
109 Maximum allowable standard deviation.
110 """
111 if display:
112 frame = 0
113 afwDisplay.Display(frame=frame).mtv(exp, title=self._testMethodName + ": Science exposure")
114 frame += 1
115 if not isinstance(fringes, list):
116 fringe = [fringes]
117 else:
118 fringe = fringes
119 for i, f in enumerate(fringe):
120 afwDisplay.Display(frame=frame).mtv(f, title=self._testMethodName
121 + ": Fringe frame %d" % (i + 1))
122 frame += 1
124 task.run(exp, fringes)
126 mi = exp.getMaskedImage()
128 if display:
129 afwDisplay.Display(frame=frame).mtv(exp, title=self._testMethodName + ": Subtracted")
130 frame += 1
132 mi -= afwMath.makeStatistics(mi, afwMath.MEAN).getValue()
133 self.assertLess(afwMath.makeStatistics(mi, afwMath.STDEV).getValue(), stddevMax)
135 def testSingle(self, pedestal=0.0, stddevMax=1.0e-4):
136 """Test subtraction of a single fringe frame.
138 Parameters
139 ----------
140 pedestal : `float`, optional
141 Pedestal to add into fringe frame
142 stddevMax : `float`, optional
143 Maximum allowable standard deviation.
144 """
145 xFreq = np.pi/10.0
146 xOffset = 1.0
147 yFreq = np.pi/15.0
148 yOffset = 0.5
149 scale = 1.0
150 fringe = createFringe(self.size, self.size, xFreq, xOffset, yFreq, yOffset)
151 fMi = fringe.getMaskedImage()
152 fMi += pedestal
153 exp = createFringe(self.size, self.size, xFreq, xOffset, yFreq, yOffset)
154 eMi = exp.getMaskedImage()
155 eMi *= scale
157 task = FringeTask(name="fringe", config=self.config)
158 self.checkFringe(task, exp, fringe, stddevMax)
160 def testBad(self, bad="BAD"):
161 """Test fringe subtraction with bad inputs.
163 Parameters
164 ----------
165 bad : `str`, optional
166 Mask plane to use.
167 """
168 xFreq = np.pi/10.0
169 xOffset = 1.0
170 yFreq = np.pi/15.0
171 yOffset = 0.5
172 fringe = createFringe(self.size, self.size, xFreq, xOffset, yFreq, yOffset)
173 exp = createFringe(self.size, self.size, xFreq, xOffset, yFreq, yOffset)
175 # This is a bad CCD: entirely masked
176 exp.maskedImage.image.set(0.0)
177 mask = exp.maskedImage.mask
178 mask.set(mask.getPlaneBitMask(bad))
180 self.config.stats.badMaskPlanes = [bad]
181 task = FringeTask(name="fringe", config=self.config)
182 task.run(exp, fringe)
183 self.assertFloatsEqual(exp.maskedImage.image.array, 0.0)
185 def testPedestal(self):
186 """Test subtraction of a fringe frame with a pedestal.
187 """
188 self.config.pedestal = True
189 self.testSingle(pedestal=10000.0, stddevMax=1.0e-3) # Not sure why this produces worse sttdev
190 self.testMultiple(pedestal=10000.0)
192 def testMultiple(self, pedestal=0.0):
193 """Test subtraction of multiple fringe frames
195 Paramters
196 ---------
197 pedestal : `float`, optional
198 Pedestal to add into fringe frame.
199 """
200 xFreqList = [0.1, 0.13, 0.06]
201 xOffsetList = [0.0, 0.1, 0.2]
202 yFreqList = [0.09, 0.12, 0.07]
203 yOffsetList = [0.3, 0.2, 0.1]
204 fringeList = [createFringe(self.size, self.size, xFreq, xOffset, yFreq, yOffset)
205 for xFreq, xOffset, yFreq, yOffset in
206 zip(xFreqList, xOffsetList, yFreqList, yOffsetList)]
208 for fringe in fringeList:
209 fMi = fringe.getMaskedImage()
210 fMi += pedestal
211 # Generate science frame
212 scales = [0.33, 0.33, 0.33]
213 image = afwImage.ImageF(self.size, self.size)
214 image.set(0)
215 for s, f in zip(scales, fringeList):
216 image.scaledPlus(s, f.getMaskedImage().getImage())
217 mi = afwImage.makeMaskedImage(image)
218 exp = afwImage.makeExposure(mi)
219 exp.setFilterLabel(afwImage.FilterLabel(physical='FILTER'))
221 task = FringeTask(name="multiFringe", config=self.config)
222 self.checkFringe(task, exp, fringeList, stddevMax=1.0e-2)
224 def testRunDataRef(self, pedestal=0.0, stddevMax=1.0e-4):
225 """Test the .runDataRef method for complete test converage.
227 Paramters
228 ---------
229 pedestal : `float`, optional
230 Pedestal to add into fringe frame.
231 stddevMax : `float`, optional
232 Maximum allowable standard deviation.
233 """
234 xFreq = np.pi/10.0
235 xOffset = 1.0
236 yFreq = np.pi/15.0
237 yOffset = 0.5
238 scale = 1.0
239 fringe = createFringe(self.size, self.size, xFreq, xOffset, yFreq, yOffset)
240 fMi = fringe.getMaskedImage()
241 fMi += pedestal
242 exp = createFringe(self.size, self.size, xFreq, xOffset, yFreq, yOffset)
243 eMi = exp.getMaskedImage()
244 eMi *= scale
246 task = FringeTask(name="fringe", config=self.config)
247 dataRef = FringeDataRef(fringe)
248 task.runDataRef(exp, dataRef)
250 mi = exp.getMaskedImage()
251 mi -= afwMath.makeStatistics(mi, afwMath.MEAN).getValue()
252 self.assertLess(afwMath.makeStatistics(mi, afwMath.STDEV).getValue(), stddevMax)
254 def test_readFringes(self):
255 """Test that fringes can be successfully accessed from the butler.
256 """
257 task = FringeTask()
258 dataRef = isrMock.DataRefMock()
260 result = task.readFringes(dataRef, assembler=None)
261 self.assertIsInstance(result, pipeBase.Struct)
263 def test_multiFringes(self):
264 """Test that multi-fringe results are handled correctly by the task.
265 """
266 self.config.large = 16
267 task = FringeTask(name="multiFringeMock", config=self.config)
269 config = isrMock.IsrMockConfig()
270 config.fringeScale = [750.0, 240.0, 220.0]
271 config.fringeX0 = [100.0, 150.0, 200.0]
272 config.fringeY0 = [0.0, 200.0, 0.0]
273 dataRef = isrMock.FringeDataRefMock(config=config)
275 exp = dataRef.get("raw")
276 exp.setFilterLabel(afwImage.FilterLabel(physical='FILTER'))
277 medianBefore = np.nanmedian(exp.getImage().getArray())
278 fringes = task.readFringes(dataRef, assembler=None)
280 solution, rms = task.run(exp, **fringes.getDict())
281 medianAfter = np.nanmedian(exp.getImage().getArray())
282 stdAfter = np.nanstd(exp.getImage().getArray())
284 self.assertLess(medianAfter, medianBefore)
285 self.assertFloatsAlmostEqual(medianAfter, 3000.925, atol=1e-4)
286 self.assertFloatsAlmostEqual(stdAfter, 3549.9885, atol=1e-4)
288 deviation = np.abs(solution - config.fringeScale)
289 self.assertTrue(np.all(deviation / rms < 1.0))
292class MemoryTester(lsst.utils.tests.MemoryTestCase):
293 pass
296def setup_module(module):
297 lsst.utils.tests.init()
300if __name__ == "__main__": 300 ↛ 301line 300 didn't jump to line 301, because the condition on line 300 was never true
301 lsst.utils.tests.init()
302 unittest.main()