Coverage for tests/test_fringes.py: 16%

146 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-04 03:25 -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/>. 

21 

22import unittest 

23 

24import numpy as np 

25 

26import lsst.utils.tests 

27import lsst.afw.math as afwMath 

28import lsst.afw.image as afwImage 

29 

30from lsst.ip.isr.fringe import FringeTask 

31 

32import lsst.ip.isr.isrMock as isrMock 

33 

34try: 

35 display 

36except NameError: 

37 display = False 

38else: 

39 import lsst.afw.display as afwDisplay 

40 afwDisplay.setDefaultMaskTransparency(75) 

41 

42 

43def createFringe(width, height, xFreq, xOffset, yFreq, yOffset): 

44 """Create a fringe frame. 

45 

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. 

54 

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 

68 

69 

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 

82 

83 def checkFringe(self, task, exp, fringes, stddevMax): 

84 """Run fringe subtraction and verify. 

85 

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 

109 

110 task.run(exp, fringes) 

111 

112 mi = exp.getMaskedImage() 

113 

114 if display: 

115 afwDisplay.Display(frame=frame).mtv(exp, title=self._testMethodName + ": Subtracted") 

116 frame += 1 

117 

118 mi -= afwMath.makeStatistics(mi, afwMath.MEAN).getValue() 

119 self.assertLess(afwMath.makeStatistics(mi, afwMath.STDEV).getValue(), stddevMax) 

120 

121 def testSingle(self, pedestal=0.0, stddevMax=1.0e-4): 

122 """Test subtraction of a single fringe frame. 

123 

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 

142 

143 task = FringeTask(name="fringe", config=self.config) 

144 self.checkFringe(task, exp, fringe, stddevMax) 

145 

146 def testBad(self, bad="BAD"): 

147 """Test fringe subtraction with bad inputs. 

148 

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) 

160 

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)) 

165 

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) 

170 

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) 

177 

178 def testMultiple(self, pedestal=0.0): 

179 """Test subtraction of multiple fringe frames 

180 

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)] 

193 

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')) 

206 

207 task = FringeTask(name="multiFringe", config=self.config) 

208 self.checkFringe(task, exp, fringeList, stddevMax=1.0e-2) 

209 

210 def testRun(self, pedestal=0.0, stddevMax=1.0e-4): 

211 """Test the .run method for complete test converage. 

212 

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 

231 

232 task = FringeTask(name="fringe", config=self.config) 

233 task.run(exp, fringe) 

234 

235 mi = exp.getMaskedImage() 

236 mi -= afwMath.makeStatistics(mi, afwMath.MEAN).getValue() 

237 self.assertLess(afwMath.makeStatistics(mi, afwMath.STDEV).getValue(), stddevMax) 

238 

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) 

244 

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] 

249 

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) 

256 

257 solution, rms = task.run(exp, **fringes.getDict()) 

258 medianAfter = np.nanmedian(exp.getImage().getArray()) 

259 stdAfter = np.nanstd(exp.getImage().getArray()) 

260 

261 self.assertLess(medianAfter, medianBefore) 

262 self.assertFloatsAlmostEqual(medianAfter, 3000.925, atol=1e-4) 

263 self.assertFloatsAlmostEqual(stdAfter, 3549.9885, atol=1e-4) 

264 

265 deviation = np.abs(solution - config.fringeScale) 

266 self.assertTrue(np.all(deviation / rms < 1.0)) 

267 

268 

269class MemoryTester(lsst.utils.tests.MemoryTestCase): 

270 pass 

271 

272 

273def setup_module(module): 

274 lsst.utils.tests.init() 

275 

276 

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()