Coverage for tests/test_isrFunctions.py: 20%

149 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2022-08-28 08:49 +0000

1# 

2# LSST Data Management System 

3# Copyright 2008-2017 AURA/LSST. 

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 <https://www.lsstcorp.org/LegalNotices/>. 

21# 

22 

23import unittest 

24import numpy as np 

25 

26import lsst.afw.image as afwImage 

27import lsst.utils.tests 

28import lsst.ip.isr as ipIsr 

29import lsst.ip.isr.isrMock as isrMock 

30 

31 

32def computeImageMedianAndStd(image): 

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

34 

35 Parameters 

36 ---------- 

37 image : `lsst.afw.image.Image` 

38 Image to measure statistics on. 

39 

40 Returns 

41 ------- 

42 median : `float` 

43 Image median. 

44 std : `float` 

45 Image stddev. 

46 """ 

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

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

49 

50 return (median, std) 

51 

52 

53def countMaskedPixels(maskedImage, maskPlane): 

54 """Function to count the number of masked pixels of a given type. 

55 

56 Parameters 

57 ---------- 

58 maskedImage : `lsst.afw.image.MaskedImage` 

59 Image to measure the mask on. 

60 maskPlane : `str` 

61 Name of the mask plane to count 

62 

63 Returns 

64 ------- 

65 nMask : `int` 

66 Number of masked pixels. 

67 """ 

68 bitMask = maskedImage.getMask().getPlaneBitMask(maskPlane) 

69 isBit = maskedImage.getMask().getArray() & bitMask > 0 

70 numBit = np.sum(isBit) 

71 

72 return numBit 

73 

74 

75class IsrFunctionsCases(lsst.utils.tests.TestCase): 

76 """Test that functions for ISR produce expected outputs. 

77 """ 

78 def setUp(self): 

79 self.inputExp = isrMock.TrimmedRawMock().run() 

80 self.mi = self.inputExp.getMaskedImage() 

81 

82 def test_transposeMaskedImage(self): 

83 """Expect height and width to be exchanged. 

84 """ 

85 transposed = ipIsr.transposeMaskedImage(self.mi) 

86 self.assertEqual(transposed.getImage().getBBox().getHeight(), 

87 self.mi.getImage().getBBox().getWidth()) 

88 self.assertEqual(transposed.getImage().getBBox().getWidth(), 

89 self.mi.getImage().getBBox().getHeight()) 

90 

91 def test_interpolateDefectList(self): 

92 """Expect number of interpolated pixels to be non-zero. 

93 """ 

94 defectList = isrMock.DefectMock().run() 

95 self.assertEqual(len(defectList), 1) 

96 

97 for fallbackValue in (None, -999.0): 

98 for haveMask in (True, False): 

99 with self.subTest(fallbackValue=fallbackValue, haveMask=haveMask): 

100 if haveMask is False: 

101 if 'INTRP' in self.mi.getMask().getMaskPlaneDict(): 

102 self.mi.getMask().removeAndClearMaskPlane('INTRP') 

103 else: 

104 if 'INTRP' not in self.mi.getMask().getMaskPlaneDict(): 

105 self.mi.getMask().addMaskPlane('INTRP') 

106 numBit = countMaskedPixels(self.mi, "INTRP") 

107 self.assertEqual(numBit, 0) 

108 

109 def test_transposeDefectList(self): 

110 """Expect bbox dimension values to flip. 

111 """ 

112 defectList = isrMock.DefectMock().run() 

113 transposed = defectList.transpose() 

114 

115 for d, t in zip(defectList, transposed): 

116 self.assertEqual(d.getBBox().getDimensions().getX(), t.getBBox().getDimensions().getY()) 

117 self.assertEqual(d.getBBox().getDimensions().getY(), t.getBBox().getDimensions().getX()) 

118 

119 def test_makeThresholdMask(self): 

120 """Expect list of defects to have elements. 

121 """ 

122 defectList = ipIsr.makeThresholdMask(self.mi, 200, 

123 growFootprints=2, 

124 maskName='SAT') 

125 

126 self.assertEqual(len(defectList), 1) 

127 

128 def test_interpolateFromMask(self): 

129 """Expect number of interpolated pixels to be non-zero. 

130 """ 

131 ipIsr.makeThresholdMask(self.mi, 200, growFootprints=2, 

132 maskName='SAT') 

133 for growFootprints in range(0, 3): 

134 interpMaskedImage = ipIsr.interpolateFromMask(self.mi, 2.0, 

135 growSaturatedFootprints=growFootprints, 

136 maskNameList=['SAT']) 

137 numBit = countMaskedPixels(interpMaskedImage, "INTRP") 

138 self.assertEqual(numBit, 40800, msg=f"interpolateFromMask with growFootprints={growFootprints}") 

139 

140 def test_saturationCorrectionInterpolate(self): 

141 """Expect number of mask pixels with SAT marked to be non-zero. 

142 """ 

143 corrMaskedImage = ipIsr.saturationCorrection(self.mi, 200, 2.0, 

144 growFootprints=2, interpolate=True, 

145 maskName='SAT') 

146 numBit = countMaskedPixels(corrMaskedImage, "SAT") 

147 self.assertEqual(numBit, 40800) 

148 

149 def test_saturationCorrectionNoInterpolate(self): 

150 """Expect number of mask pixels with SAT marked to be non-zero. 

151 """ 

152 corrMaskedImage = ipIsr.saturationCorrection(self.mi, 200, 2.0, 

153 growFootprints=2, interpolate=False, 

154 maskName='SAT') 

155 numBit = countMaskedPixels(corrMaskedImage, "SAT") 

156 self.assertEqual(numBit, 40800) 

157 

158 def test_trimToMatchCalibBBox(self): 

159 """Expect bounding boxes to match. 

160 """ 

161 darkExp = isrMock.DarkMock().run() 

162 darkMi = darkExp.getMaskedImage() 

163 

164 nEdge = 2 

165 darkMi = darkMi[nEdge:-nEdge, nEdge:-nEdge, afwImage.LOCAL] 

166 newInput = ipIsr.trimToMatchCalibBBox(self.mi, darkMi) 

167 

168 self.assertEqual(newInput.getImage().getBBox(), darkMi.getImage().getBBox()) 

169 

170 def test_darkCorrection(self): 

171 """Expect round-trip application to be equal. 

172 Expect RuntimeError if sizes are different. 

173 """ 

174 darkExp = isrMock.DarkMock().run() 

175 darkMi = darkExp.getMaskedImage() 

176 

177 mi = self.mi.clone() 

178 

179 # The `invert` parameter controls the direction of the 

180 # application. This will apply, and un-apply the dark. 

181 ipIsr.darkCorrection(self.mi, darkMi, 1.0, 1.0, trimToFit=True) 

182 ipIsr.darkCorrection(self.mi, darkMi, 1.0, 1.0, trimToFit=True, invert=True) 

183 

184 self.assertMaskedImagesAlmostEqual(self.mi, mi, atol=1e-3) 

185 

186 darkMi = darkMi[1:-1, 1:-1, afwImage.LOCAL] 

187 with self.assertRaises(RuntimeError): 

188 ipIsr.darkCorrection(self.mi, darkMi, 1.0, 1.0, trimToFit=False) 

189 

190 def test_biasCorrection(self): 

191 """Expect smaller median image value after. 

192 Expect RuntimeError if sizes are different. 

193 """ 

194 biasExp = isrMock.BiasMock().run() 

195 biasMi = biasExp.getMaskedImage() 

196 

197 mi = self.mi.clone() 

198 ipIsr.biasCorrection(self.mi, biasMi, trimToFit=True) 

199 self.assertLess(computeImageMedianAndStd(self.mi.getImage())[0], 

200 computeImageMedianAndStd(mi.getImage())[0]) 

201 

202 biasMi = biasMi[1:-1, 1:-1, afwImage.LOCAL] 

203 with self.assertRaises(RuntimeError): 

204 ipIsr.biasCorrection(self.mi, biasMi, trimToFit=False) 

205 

206 def test_flatCorrection(self): 

207 """Expect round-trip application to be equal. 

208 Expect RuntimeError if sizes are different. 

209 """ 

210 flatExp = isrMock.FlatMock().run() 

211 flatMi = flatExp.getMaskedImage() 

212 

213 mi = self.mi.clone() 

214 for scaling in ('USER', 'MEAN', 'MEDIAN'): 

215 # The `invert` parameter controls the direction of the 

216 # application. This will apply, and un-apply the flat. 

217 ipIsr.flatCorrection(self.mi, flatMi, scaling, userScale=1.0, trimToFit=True) 

218 ipIsr.flatCorrection(self.mi, flatMi, scaling, userScale=1.0, 

219 trimToFit=True, invert=True) 

220 

221 self.assertMaskedImagesAlmostEqual(self.mi, mi, atol=1e-3, 

222 msg=f"flatCorrection with scaling {scaling}") 

223 

224 flatMi = flatMi[1:-1, 1:-1, afwImage.LOCAL] 

225 with self.assertRaises(RuntimeError): 

226 ipIsr.flatCorrection(self.mi, flatMi, 'USER', userScale=1.0, trimToFit=False) 

227 

228 def test_flatCorrectionUnknown(self): 

229 """Raise if an unknown scaling is used. 

230 

231 The `scaling` parameter must be a known type. If not, the 

232 flat correction will raise a RuntimeError. 

233 """ 

234 flatExp = isrMock.FlatMock().run() 

235 flatMi = flatExp.getMaskedImage() 

236 

237 with self.assertRaises(RuntimeError): 

238 ipIsr.flatCorrection(self.mi, flatMi, "UNKNOWN", userScale=1.0, trimToFit=True) 

239 

240 def test_illumCorrection(self): 

241 """Expect larger median value after. 

242 Expect RuntimeError if sizes are different. 

243 """ 

244 flatExp = isrMock.FlatMock().run() 

245 flatMi = flatExp.getMaskedImage() 

246 

247 mi = self.mi.clone() 

248 ipIsr.illuminationCorrection(self.mi, flatMi, 1.0) 

249 self.assertGreater(computeImageMedianAndStd(self.mi.getImage())[0], 

250 computeImageMedianAndStd(mi.getImage())[0]) 

251 

252 flatMi = flatMi[1:-1, 1:-1, afwImage.LOCAL] 

253 with self.assertRaises(RuntimeError): 

254 ipIsr.illuminationCorrection(self.mi, flatMi, 1.0, trimToFit=False) 

255 

256 def test_brighterFatterCorrection(self): 

257 """Expect smoother image/smaller std before. 

258 """ 

259 bfKern = isrMock.BfKernelMock().run() 

260 

261 before = computeImageMedianAndStd(self.inputExp.getImage()) 

262 ipIsr.brighterFatterCorrection(self.inputExp, bfKern, 10, 1e-2, False) 

263 after = computeImageMedianAndStd(self.inputExp.getImage()) 

264 

265 self.assertLess(before[1], after[1]) 

266 

267 def test_gainContext(self): 

268 """Expect image to be unmodified before and after 

269 """ 

270 mi = self.inputExp.getMaskedImage().clone() 

271 with ipIsr.gainContext(self.inputExp, self.inputExp.getImage(), apply=True): 

272 pass 

273 

274 self.assertIsNotNone(mi) 

275 self.assertMaskedImagesAlmostEqual(self.inputExp.getMaskedImage(), mi) 

276 

277 def test_widenSaturationTrails(self): 

278 """Expect more mask pixels with SAT set after. 

279 """ 

280 numBitBefore = countMaskedPixels(self.mi, "SAT") 

281 

282 ipIsr.widenSaturationTrails(self.mi.getMask()) 

283 numBitAfter = countMaskedPixels(self.mi, "SAT") 

284 

285 self.assertGreaterEqual(numBitAfter, numBitBefore) 

286 

287 def test_setBadRegions(self): 

288 """Expect RuntimeError if improper statistic given. 

289 Expect a float value otherwise. 

290 """ 

291 for badStatistic in ('MEDIAN', 'MEANCLIP', 'UNKNOWN'): 

292 if badStatistic == 'UNKNOWN': 

293 with self.assertRaises(RuntimeError, 

294 msg=f"setBadRegions did not fail for stat {badStatistic}"): 

295 nBad, value = ipIsr.setBadRegions(self.inputExp, badStatistic=badStatistic) 

296 else: 

297 nBad, value = ipIsr.setBadRegions(self.inputExp, badStatistic=badStatistic) 

298 self.assertGreaterEqual(abs(value), 0.0, 

299 msg=f"setBadRegions did not find valid value for stat {badStatistic}") 

300 

301 def test_attachTransmissionCurve(self): 

302 """Expect no failure and non-None output from attachTransmissionCurve. 

303 """ 

304 curve = isrMock.TransmissionMock().run() 

305 combined = ipIsr.attachTransmissionCurve(self.inputExp, 

306 opticsTransmission=curve, 

307 filterTransmission=curve, 

308 sensorTransmission=curve, 

309 atmosphereTransmission=curve) 

310 # DM-19707: ip_isr functionality not fully tested by unit tests 

311 self.assertIsNotNone(combined) 

312 

313 def test_attachTransmissionCurve_None(self): 

314 """Expect no failure and non-None output from attachTransmissionCurve. 

315 """ 

316 curve = None 

317 combined = ipIsr.attachTransmissionCurve(self.inputExp, 

318 opticsTransmission=curve, 

319 filterTransmission=curve, 

320 sensorTransmission=curve, 

321 atmosphereTransmission=curve) 

322 # DM-19707: ip_isr functionality not fully tested by unit tests 

323 self.assertIsNotNone(combined) 

324 

325 

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

327 pass 

328 

329 

330def setup_module(module): 

331 lsst.utils.tests.init() 

332 

333 

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

335 lsst.utils.tests.init() 

336 unittest.main()