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