Coverage for tests/test_overscanCorrection.py: 11%

218 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2022-09-02 09:57 +0000

1# 

2# LSST Data Management System 

3# Copyright 2008, 2009, 2010 LSST Corporation. 

4# 

5# This product includes software developed by the 

6# LSST Project (http://www.lsst.org/). 

7# 

8# This program is free software: you can redistribute it and/or modify 

9# it under the terms of the GNU General Public License as published by 

10# the Free Software Foundation, either version 3 of the License, or 

11# (at your option) any later version. 

12# 

13# This program is distributed in the hope that it will be useful, 

14# but WITHOUT ANY WARRANTY; without even the implied warranty of 

15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

16# GNU General Public License for more details. 

17# 

18# You should have received a copy of the LSST License Statement and 

19# the GNU General Public License along with this program. If not, 

20# see <http://www.lsstcorp.org/LegalNotices/>. 

21# 

22 

23import unittest 

24import numpy as np 

25 

26import lsst.utils.tests 

27import lsst.geom 

28import lsst.afw.image as afwImage 

29import lsst.afw.cameraGeom as cameraGeom 

30import lsst.ip.isr as ipIsr 

31import lsst.pipe.base as pipeBase 

32 

33 

34def computeImageMedianAndStd(image): 

35 """Function to calculate median and std of image data. 

36 

37 Parameters 

38 ---------- 

39 image : `lsst.afw.image.Image` 

40 Image to measure statistics on. 

41 

42 Returns 

43 ------- 

44 median : `float` 

45 Image median. 

46 std : `float` 

47 Image stddev. 

48 """ 

49 median = np.nanmedian(image.getArray()) 

50 std = np.nanstd(image.getArray()) 

51 

52 return (median, std) 

53 

54 

55class IsrTestCases(lsst.utils.tests.TestCase): 

56 

57 def updateConfigFromKwargs(self, config, **kwargs): 

58 """Common config from keywords. 

59 """ 

60 fitType = kwargs.get('fitType', None) 

61 if fitType: 

62 config.overscan.fitType = fitType 

63 

64 order = kwargs.get('order', None) 

65 if order: 

66 config.overscan.order = order 

67 

68 def makeExposure(self, isTransposed=False): 

69 # Define the camera geometry we'll use. 

70 cameraBuilder = cameraGeom.Camera.Builder("Fake Camera") 

71 detectorBuilder = cameraBuilder.add("Fake amp", 0) 

72 

73 ampBuilder = cameraGeom.Amplifier.Builder() 

74 

75 dataBBox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0), 

76 lsst.geom.Extent2I(10, 10)) 

77 

78 if isTransposed is True: 

79 fullBBox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0), 

80 lsst.geom.Point2I(9, 12)) 

81 overscanBBox = lsst.geom.Box2I(lsst.geom.Point2I(0, 10), 

82 lsst.geom.Point2I(9, 12)) 

83 else: 

84 fullBBox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0), 

85 lsst.geom.Point2I(12, 9)) 

86 

87 overscanBBox = lsst.geom.Box2I(lsst.geom.Point2I(10, 0), 

88 lsst.geom.Point2I(12, 9)) 

89 

90 ampBuilder.setRawBBox(fullBBox) 

91 ampBuilder.setRawSerialOverscanBBox(overscanBBox) 

92 ampBuilder.setRawDataBBox(dataBBox) 

93 

94 detectorBuilder.append(ampBuilder) 

95 camera = cameraBuilder.finish() 

96 detector = camera[0] 

97 

98 # Define image data. 

99 maskedImage = afwImage.MaskedImageF(fullBBox) 

100 maskedImage.set(10, 0x0, 1) 

101 

102 overscan = afwImage.MaskedImageF(maskedImage, overscanBBox) 

103 overscan.set(2, 0x0, 1) 

104 

105 exposure = afwImage.ExposureF(maskedImage, None) 

106 exposure.setDetector(detector) 

107 return exposure 

108 

109 def checkOverscanCorrectionY(self, **kwargs): 

110 exposure = self.makeExposure(isTransposed=True) 

111 detector = exposure.getDetector() 

112 

113 # These subimages are needed below. 

114 overscan = exposure[detector.getAmplifiers()[0].getRawSerialOverscanBBox()] 

115 maskedImage = exposure[detector.getAmplifiers()[0].getRawBBox()] 

116 

117 config = ipIsr.IsrTask.ConfigClass() 

118 self.updateConfigFromKwargs(config, **kwargs) 

119 

120 if kwargs['fitType'] == "MEDIAN_PER_ROW": 

121 # Add a bad point to test outlier rejection. 

122 overscan.getImage().getArray()[0, 0] = 12345 

123 

124 # Shrink the sigma clipping limit to handle the fact that the 

125 # bad point is not be rejected at higher thresholds (2/0.74). 

126 config.overscan.numSigmaClip = 2.7 

127 

128 isrTask = ipIsr.IsrTask(config=config) 

129 isrTask.overscan.run(exposure, detector.getAmplifiers()[0], isTransposed=True) 

130 

131 height = maskedImage.getHeight() 

132 width = maskedImage.getWidth() 

133 for j in range(height): 

134 for i in range(width): 

135 if j == 10 and i == 0 and kwargs['fitType'] == "MEDIAN_PER_ROW": 

136 self.assertEqual(maskedImage.image[i, j, afwImage.LOCAL], 12343) 

137 elif j >= 10: 

138 self.assertEqual(maskedImage.image[i, j, afwImage.LOCAL], 0) 

139 else: 

140 self.assertEqual(maskedImage.image[i, j, afwImage.LOCAL], 8) 

141 

142 def checkOverscanCorrectionX(self, **kwargs): 

143 exposure = self.makeExposure(isTransposed=False) 

144 detector = exposure.getDetector() 

145 

146 # These subimages are needed below. 

147 maskedImage = exposure[detector.getAmplifiers()[0].getRawBBox()] 

148 

149 config = ipIsr.IsrTask.ConfigClass() 

150 self.updateConfigFromKwargs(config, **kwargs) 

151 

152 isrTask = ipIsr.IsrTask(config=config) 

153 isrTask.overscan.run(exposure, detector.getAmplifiers()[0], isTransposed=False) 

154 

155 height = maskedImage.getHeight() 

156 width = maskedImage.getWidth() 

157 for j in range(height): 

158 for i in range(width): 

159 if i >= 10: 

160 self.assertEqual(maskedImage.image[i, j, afwImage.LOCAL], 0) 

161 else: 

162 self.assertEqual(maskedImage.image[i, j, afwImage.LOCAL], 8) 

163 

164 def checkOverscanCorrectionSineWave(self, **kwargs): 

165 """vertical sine wave along long direction""" 

166 # Define the camera geometry we'll use. 

167 cameraBuilder = cameraGeom.Camera.Builder("Fake Camera") 

168 detectorBuilder = cameraBuilder.add("Fake amp", 0) 

169 

170 ampBuilder = cameraGeom.Amplifier.Builder() 

171 

172 dataBBox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0), 

173 lsst.geom.Extent2I(70, 500)) 

174 

175 fullBBox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0), 

176 lsst.geom.Extent2I(100, 500)) 

177 

178 overscanBBox = lsst.geom.Box2I(lsst.geom.Point2I(70, 0), 

179 lsst.geom.Extent2I(30, 500)) 

180 

181 ampBuilder.setRawBBox(fullBBox) 

182 ampBuilder.setRawSerialOverscanBBox(overscanBBox) 

183 ampBuilder.setRawDataBBox(dataBBox) 

184 

185 detectorBuilder.append(ampBuilder) 

186 camera = cameraBuilder.finish() 

187 detector = camera[0] 

188 

189 # Define image data. 

190 maskedImage = afwImage.MaskedImageF(fullBBox) 

191 maskedImage.set(50, 0x0, 1) 

192 

193 overscan = afwImage.MaskedImageF(maskedImage, overscanBBox) 

194 overscan.set(0, 0x0, 1) 

195 

196 exposure = afwImage.ExposureF(maskedImage, None) 

197 exposure.setDetector(detector) 

198 

199 # vertical sine wave along long direction 

200 x = np.linspace(0, 2*3.14159, 500) 

201 a, w = 15, 5*3.14159 

202 sineWave = 20 + a*np.sin(w*x) 

203 sineWave = sineWave.astype(int) 

204 

205 fullImage = np.repeat(sineWave, 100).reshape((500, 100)) 

206 maskedImage.image.array += fullImage 

207 

208 config = ipIsr.IsrTask.ConfigClass() 

209 self.updateConfigFromKwargs(config, **kwargs) 

210 

211 isrTask = ipIsr.IsrTask(config=config) 

212 isrTask.overscan.run(exposure, detector.getAmplifiers()[0]) 

213 

214 height = maskedImage.getHeight() 

215 width = maskedImage.getWidth() 

216 

217 for j in range(height): 

218 for i in range(width): 

219 if i >= 70: 

220 self.assertEqual(maskedImage.image[i, j, afwImage.LOCAL], 0.0) 

221 else: 

222 self.assertEqual(maskedImage.image[i, j, afwImage.LOCAL], 50.0) 

223 

224 def test_MedianPerRowOverscanCorrection(self): 

225 self.checkOverscanCorrectionY(fitType="MEDIAN_PER_ROW") 

226 self.checkOverscanCorrectionX(fitType="MEDIAN_PER_ROW") 

227 self.checkOverscanCorrectionSineWave(fitType="MEDIAN_PER_ROW") 

228 

229 def test_MedianOverscanCorrection(self): 

230 self.checkOverscanCorrectionY(fitType="MEDIAN") 

231 self.checkOverscanCorrectionX(fitType="MEDIAN") 

232 

233 def checkPolyOverscanCorrectionX(self, **kwargs): 

234 exposure = self.makeExposure(isTransposed=False) 

235 detector = exposure.getDetector() 

236 

237 # These subimages are needed below. 

238 overscan = exposure[detector.getAmplifiers()[0].getRawSerialOverscanBBox()] 

239 maskedImage = exposure[detector.getAmplifiers()[0].getRawBBox()] 

240 

241 bbox = detector.getAmplifiers()[0].getRawSerialOverscanBBox() 

242 overscan.getMaskedImage().set(2, 0x0, 1) 

243 for i in range(bbox.getDimensions()[1]): 

244 for j, off in enumerate([-0.5, 0.0, 0.5]): 

245 overscan.image[j, i, afwImage.LOCAL] = 2+i+off 

246 

247 config = ipIsr.IsrTask.ConfigClass() 

248 self.updateConfigFromKwargs(config, **kwargs) 

249 

250 isrTask = ipIsr.IsrTask(config=config) 

251 isrTask.overscan.run(exposure, detector.getAmplifiers()[0], isTransposed=False) 

252 

253 height = maskedImage.getHeight() 

254 width = maskedImage.getWidth() 

255 for j in range(height): 

256 for i in range(width): 

257 if i == 10: 

258 self.assertEqual(maskedImage.image[i, j, afwImage.LOCAL], -0.5) 

259 elif i == 11: 

260 self.assertEqual(maskedImage.image[i, j, afwImage.LOCAL], 0) 

261 elif i == 12: 

262 self.assertEqual(maskedImage.image[i, j, afwImage.LOCAL], 0.5) 

263 else: 

264 self.assertEqual(maskedImage.image[i, j, afwImage.LOCAL], 10 - 2 - j) 

265 

266 def checkPolyOverscanCorrectionY(self, **kwargs): 

267 exposure = self.makeExposure(isTransposed=True) 

268 detector = exposure.getDetector() 

269 

270 # These subimages are needed below. 

271 overscan = exposure[detector.getAmplifiers()[0].getRawSerialOverscanBBox()] 

272 maskedImage = exposure[detector.getAmplifiers()[0].getRawBBox()] 

273 

274 bbox = detector.getAmplifiers()[0].getRawSerialOverscanBBox() 

275 overscan.getMaskedImage().set(2, 0x0, 1) 

276 for i in range(bbox.getDimensions()[0]): 

277 for j, off in enumerate([-0.5, 0.0, 0.5]): 

278 overscan.image[i, j, afwImage.LOCAL] = 2+i+off 

279 # maskedImage.getMaskedImage().set(10, 0x0, 1) 

280 

281 config = ipIsr.IsrTask.ConfigClass() 

282 self.updateConfigFromKwargs(config, **kwargs) 

283 

284 isrTask = ipIsr.IsrTask(config=config) 

285 isrTask.overscan.run(exposure, detector.getAmplifiers()[0], isTransposed=True) 

286 

287 height = maskedImage.getHeight() 

288 width = maskedImage.getWidth() 

289 for j in range(height): 

290 for i in range(width): 

291 if j == 10: 

292 self.assertEqual(maskedImage.image[i, j, afwImage.LOCAL], -0.5) 

293 elif j == 11: 

294 self.assertEqual(maskedImage.image[i, j, afwImage.LOCAL], 0) 

295 elif j == 12: 

296 self.assertEqual(maskedImage.image[i, j, afwImage.LOCAL], 0.5) 

297 else: 

298 self.assertEqual(maskedImage.image[i, j, afwImage.LOCAL], 10 - 2 - i) 

299 

300 def test_PolyOverscanCorrection(self): 

301 for fitType in ("POLY", "CHEB", "LEG"): 

302 self.checkPolyOverscanCorrectionX(fitType=fitType, order=5) 

303 self.checkPolyOverscanCorrectionY(fitType=fitType, order=5) 

304 

305 def test_SplineOverscanCorrection(self): 

306 for fitType in ("NATURAL_SPLINE", "CUBIC_SPLINE", "AKIMA_SPLINE"): 

307 self.checkPolyOverscanCorrectionX(fitType=fitType, order=5) 

308 self.checkPolyOverscanCorrectionY(fitType=fitType, order=5) 

309 

310 def test_overscanCorrection(self): 

311 """Expect that this should reduce the image variance with a full fit. 

312 The default fitType of MEDIAN will reduce the median value. 

313 

314 This needs to operate on a RawMock() to have overscan data to use. 

315 

316 The output types may be different when fitType != MEDIAN. 

317 """ 

318 exposure = self.makeExposure(isTransposed=False) 

319 detector = exposure.getDetector() 

320 amp = detector.getAmplifiers()[0] 

321 

322 statBefore = computeImageMedianAndStd(exposure.image[amp.getRawDataBBox()]) 

323 

324 config = ipIsr.IsrTask.ConfigClass() 

325 isrTask = ipIsr.IsrTask(config=config) 

326 oscanResults = isrTask.overscan.run(exposure, amp) 

327 

328 self.assertIsInstance(oscanResults, pipeBase.Struct) 

329 self.assertIsInstance(oscanResults.imageFit, float) 

330 self.assertIsInstance(oscanResults.overscanFit, float) 

331 self.assertIsInstance(oscanResults.overscanImage, afwImage.ExposureF) 

332 

333 statAfter = computeImageMedianAndStd(exposure.image[amp.getRawDataBBox()]) 

334 self.assertLess(statAfter[0], statBefore[0]) 

335 

336 def test_overscanCorrection_isNotInt(self): 

337 """Expect smaller median/smaller std after. 

338 Expect exception if overscan fit type isn't known. 

339 """ 

340 exposure = self.makeExposure(isTransposed=False) 

341 detector = exposure.getDetector() 

342 amp = detector.getAmplifiers()[0] 

343 

344 for fitType in ('MEAN', 'MEDIAN', 'MEDIAN_PER_ROW', 'MEANCLIP', 'POLY', 'CHEB', 

345 'NATURAL_SPLINE', 'CUBIC_SPLINE'): 

346 if fitType in ('NATURAL_SPLINE', 'CUBIC_SPLINE'): 

347 order = 3 

348 else: 

349 order = 1 

350 config = ipIsr.IsrTask.ConfigClass() 

351 config.overscan.order = order 

352 config.overscan.fitType = fitType 

353 isrTask = ipIsr.IsrTask(config=config) 

354 

355 response = isrTask.overscan.run(exposure, amp) 

356 

357 self.assertIsInstance(response, pipeBase.Struct, 

358 msg=f"overscanCorrection overscanIsNotInt Bad response: {fitType}") 

359 self.assertIsNotNone(response.imageFit, 

360 msg=f"overscanCorrection overscanIsNotInt Bad imageFit: {fitType}") 

361 self.assertIsNotNone(response.overscanFit, 

362 msg=f"overscanCorrection overscanIsNotInt Bad overscanFit: {fitType}") 

363 self.assertIsInstance(response.overscanImage, afwImage.ExposureF, 

364 msg=f"overscanCorrection overscanIsNotInt Bad overscanImage: {fitType}") 

365 

366 

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

368 pass 

369 

370 

371def setup_module(module): 

372 lsst.utils.tests.init() 

373 

374 

375if __name__ == "__main__": 375 ↛ 376line 375 didn't jump to line 376, because the condition on line 375 was never true

376 lsst.utils.tests.init() 

377 unittest.main()