Coverage for tests/test_isrFunctions.py: 17%

167 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-03-05 10:29 +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.geom as geom 

27import lsst.afw.image as afwImage 

28import lsst.utils.tests 

29import lsst.ip.isr as ipIsr 

30import lsst.ip.isr.isrMock as isrMock 

31 

32 

33def computeImageMedianAndStd(image): 

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

35 

36 Parameters 

37 ---------- 

38 image : `lsst.afw.image.Image` 

39 Image to measure statistics on. 

40 

41 Returns 

42 ------- 

43 median : `float` 

44 Image median. 

45 std : `float` 

46 Image stddev. 

47 """ 

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

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

50 

51 return (median, std) 

52 

53 

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

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

56 """ 

57 def setUp(self): 

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

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

60 

61 def test_transposeMaskedImage(self): 

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

63 """ 

64 transposed = ipIsr.transposeMaskedImage(self.mi) 

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

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

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

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

69 

70 def test_interpolateDefectList(self): 

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

72 """ 

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

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

75 

76 for fallbackValue in (None, -999.0): 

77 for haveMask in (True, False): 

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

79 if haveMask is False: 

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

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

82 else: 

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

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

85 numBit = ipIsr.countMaskedPixels(self.mi, "INTRP") 

86 self.assertEqual(numBit, 0) 

87 

88 def test_transposeDefectList(self): 

89 """Expect bbox dimension values to flip. 

90 """ 

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

92 transposed = defectList.transpose() 

93 

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

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

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

97 

98 def test_makeThresholdMask(self): 

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

100 """ 

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

102 growFootprints=2, 

103 maskName='SAT') 

104 

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

106 

107 def test_interpolateFromMask(self): 

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

109 """ 

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

111 maskName='SAT') 

112 for growFootprints in range(0, 3): 

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

114 growSaturatedFootprints=growFootprints, 

115 maskNameList=['SAT']) 

116 numBit = ipIsr.countMaskedPixels(interpMaskedImage, "INTRP") 

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

118 

119 def test_saturationCorrectionInterpolate(self): 

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

121 """ 

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

123 growFootprints=2, interpolate=True, 

124 maskName='SAT') 

125 numBit = ipIsr.countMaskedPixels(corrMaskedImage, "SAT") 

126 self.assertEqual(numBit, 40800) 

127 

128 def test_saturationCorrectionNoInterpolate(self): 

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

130 """ 

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

132 growFootprints=2, interpolate=False, 

133 maskName='SAT') 

134 numBit = ipIsr.countMaskedPixels(corrMaskedImage, "SAT") 

135 self.assertEqual(numBit, 40800) 

136 

137 def test_trimToMatchCalibBBox(self): 

138 """Expect bounding boxes to match. 

139 """ 

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

141 darkMi = darkExp.getMaskedImage() 

142 

143 nEdge = 2 

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

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

146 

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

148 

149 def test_darkCorrection(self): 

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

151 Expect RuntimeError if sizes are different. 

152 """ 

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

154 darkMi = darkExp.getMaskedImage() 

155 

156 mi = self.mi.clone() 

157 

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

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

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

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

162 

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

164 

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

166 with self.assertRaises(RuntimeError): 

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

168 

169 def test_biasCorrection(self): 

170 """Expect smaller median image value after. 

171 Expect RuntimeError if sizes are different. 

172 """ 

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

174 biasMi = biasExp.getMaskedImage() 

175 

176 mi = self.mi.clone() 

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

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

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

180 

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

182 with self.assertRaises(RuntimeError): 

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

184 

185 def test_flatCorrection(self): 

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

187 Expect RuntimeError if sizes are different. 

188 """ 

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

190 flatMi = flatExp.getMaskedImage() 

191 

192 mi = self.mi.clone() 

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

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

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

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

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

198 trimToFit=True, invert=True) 

199 

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

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

202 

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

204 with self.assertRaises(RuntimeError): 

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

206 

207 def test_flatCorrectionUnknown(self): 

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

209 

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

211 flat correction will raise a RuntimeError. 

212 """ 

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

214 flatMi = flatExp.getMaskedImage() 

215 

216 with self.assertRaises(RuntimeError): 

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

218 

219 def test_illumCorrection(self): 

220 """Expect larger median value after. 

221 Expect RuntimeError if sizes are different. 

222 """ 

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

224 flatMi = flatExp.getMaskedImage() 

225 

226 mi = self.mi.clone() 

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

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

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

230 

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

232 with self.assertRaises(RuntimeError): 

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

234 

235 def test_brighterFatterCorrection(self): 

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

237 """ 

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

239 

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

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

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

243 

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

245 

246 def test_gainContext(self): 

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

248 """ 

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

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

251 pass 

252 

253 self.assertIsNotNone(mi) 

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

255 

256 def test_widenSaturationTrails(self): 

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

258 """ 

259 numBitBefore = ipIsr.countMaskedPixels(self.mi, "SAT") 

260 

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

262 numBitAfter = ipIsr.countMaskedPixels(self.mi, "SAT") 

263 

264 self.assertGreaterEqual(numBitAfter, numBitBefore) 

265 

266 def test_setBadRegions(self): 

267 """Expect RuntimeError if improper statistic given. 

268 Expect a float value otherwise. 

269 """ 

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

271 if badStatistic == 'UNKNOWN': 

272 with self.assertRaises(RuntimeError, 

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

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

275 else: 

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

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

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

279 

280 def test_attachTransmissionCurve(self): 

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

282 """ 

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

284 combined = ipIsr.attachTransmissionCurve(self.inputExp, 

285 opticsTransmission=curve, 

286 filterTransmission=curve, 

287 sensorTransmission=curve, 

288 atmosphereTransmission=curve) 

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

290 self.assertIsNotNone(combined) 

291 

292 def test_attachTransmissionCurve_None(self): 

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

294 """ 

295 curve = None 

296 combined = ipIsr.attachTransmissionCurve(self.inputExp, 

297 opticsTransmission=curve, 

298 filterTransmission=curve, 

299 sensorTransmission=curve, 

300 atmosphereTransmission=curve) 

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

302 self.assertIsNotNone(combined) 

303 

304 def test_countMaskedPixels(self): 

305 mockImageConfig = isrMock.IsrMock.ConfigClass() 

306 

307 # flatDrop is not really relevant as we replace the data 

308 # but good to note it in case we change how this image is made 

309 mockImageConfig.flatDrop = 0.99999 

310 mockImageConfig.isTrimmed = True 

311 

312 flatExp = isrMock.FlatMock(config=mockImageConfig).run() 

313 (shapeY, shapeX) = flatExp.getDimensions() 

314 

315 rng = np.random.RandomState(0) 

316 flatMean = 1000 

317 flatWidth = np.sqrt(flatMean) 

318 flatData = rng.normal(flatMean, flatWidth, (shapeX, shapeY)) 

319 flatExp.image.array[:] = flatData 

320 

321 exp = flatExp.clone() 

322 mi = exp.maskedImage 

323 self.assertEqual(ipIsr.countMaskedPixels(mi, 'NO_DATA'), 0) 

324 self.assertEqual(ipIsr.countMaskedPixels(mi, 'BAD'), 0) 

325 

326 NODATABIT = mi.mask.getPlaneBitMask("NO_DATA") 

327 noDataBox = geom.Box2I(geom.Point2I(31, 49), geom.Extent2I(3, 6)) 

328 mi.mask[noDataBox] |= NODATABIT 

329 

330 self.assertEqual(ipIsr.countMaskedPixels(mi, 'NO_DATA'), noDataBox.getArea()) 

331 self.assertEqual(ipIsr.countMaskedPixels(mi, 'BAD'), 0) 

332 

333 mi.mask[noDataBox] ^= NODATABIT # XOR to reset what we did 

334 self.assertEqual(ipIsr.countMaskedPixels(mi, 'NO_DATA'), 0) 

335 

336 

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

338 pass 

339 

340 

341def setup_module(module): 

342 lsst.utils.tests.init() 

343 

344 

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

346 lsst.utils.tests.init() 

347 unittest.main()