Coverage for tests/test_fringes.py: 17%

161 statements  

« prev     ^ index     » next       coverage.py v7.2.1, created at 2023-03-12 03:09 -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 

29import lsst.pipe.base as pipeBase 

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 

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} 

49 

50 def get(self, name="fringe", immediate=False): 

51 if name == "fringe": 

52 return self.fringe 

53 if name == "ccdExposureId": 

54 return 1000 

55 

56 

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

58 """Create a fringe frame. 

59 

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. 

68 

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 

82 

83 

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 

96 

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

98 """Run fringe subtraction and verify. 

99 

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 

123 

124 task.run(exp, fringes) 

125 

126 mi = exp.getMaskedImage() 

127 

128 if display: 

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

130 frame += 1 

131 

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

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

134 

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

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

137 

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 

156 

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

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

159 

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

161 """Test fringe subtraction with bad inputs. 

162 

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) 

174 

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

179 

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) 

184 

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) 

191 

192 def testMultiple(self, pedestal=0.0): 

193 """Test subtraction of multiple fringe frames 

194 

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

207 

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

220 

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

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

223 

224 def testRunDataRef(self, pedestal=0.0, stddevMax=1.0e-4): 

225 """Test the .runDataRef method for complete test converage. 

226 

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 

245 

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

247 dataRef = FringeDataRef(fringe) 

248 task.runDataRef(exp, dataRef) 

249 

250 mi = exp.getMaskedImage() 

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

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

253 

254 def test_readFringes(self): 

255 """Test that fringes can be successfully accessed from the butler. 

256 """ 

257 task = FringeTask() 

258 dataRef = isrMock.DataRefMock() 

259 

260 result = task.readFringes(dataRef, assembler=None) 

261 self.assertIsInstance(result, pipeBase.Struct) 

262 

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) 

268 

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) 

274 

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) 

279 

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

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

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

283 

284 self.assertLess(medianAfter, medianBefore) 

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

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

287 

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

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

290 

291 

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

293 pass 

294 

295 

296def setup_module(module): 

297 lsst.utils.tests.init() 

298 

299 

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