Coverage for tests/test_fringes.py: 17%

161 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2022-12-07 20:32 +0000

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 

45 frame. 

46 """ 

47 def __init__(self, fringe): 

48 self.fringe = fringe 

49 self.dataId = {'test': True} 

50 

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

52 if name == "fringe": 

53 return self.fringe 

54 if name == "ccdExposureId": 

55 return 1000 

56 

57 

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

59 """Create a fringe frame. 

60 

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. 

69 

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.setFilter(afwImage.FilterLabel(band='y', physical='FILTER')) 

82 return exp 

83 

84 

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 

97 

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

99 """Run fringe subtraction and verify. 

100 

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 

124 

125 task.run(exp, fringes) 

126 

127 mi = exp.getMaskedImage() 

128 

129 if display: 

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

131 frame += 1 

132 

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

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

135 

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

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

138 

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 

157 

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

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

160 

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

162 """Test fringe subtraction with bad inputs. 

163 

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) 

175 

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

180 

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) 

185 

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) 

192 

193 def testMultiple(self, pedestal=0.0): 

194 """Test subtraction of multiple fringe frames 

195 

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

208 

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.setFilter(afwImage.FilterLabel(physical='FILTER')) 

221 

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

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

224 

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

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

227 

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 

246 

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

248 dataRef = FringeDataRef(fringe) 

249 task.runDataRef(exp, dataRef) 

250 

251 mi = exp.getMaskedImage() 

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

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

254 

255 def test_readFringes(self): 

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

257 """ 

258 task = FringeTask() 

259 dataRef = isrMock.DataRefMock() 

260 

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

262 self.assertIsInstance(result, pipeBase.Struct) 

263 

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) 

269 

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) 

275 

276 exp = dataRef.get("raw") 

277 exp.setFilter(afwImage.FilterLabel(physical='FILTER')) 

278 medianBefore = np.nanmedian(exp.getImage().getArray()) 

279 fringes = task.readFringes(dataRef, assembler=None) 

280 

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

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

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

284 

285 self.assertLess(medianAfter, medianBefore) 

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

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

288 

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

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

291 

292 

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

294 pass 

295 

296 

297def setup_module(module): 

298 lsst.utils.tests.init() 

299 

300 

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