Coverage for tests/test_readers.py: 11%

215 statements  

« prev     ^ index     » next       coverage.py v7.5.0, created at 2024-04-25 00:01 -0700

1# This file is part of afw. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (http://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 <http://www.gnu.org/licenses/>. 

21 

22import unittest 

23 

24import os 

25import numpy as np 

26import astropy.io.fits 

27 

28import lsst.utils.tests 

29from lsst.daf.base import PropertyList 

30from lsst.geom import Box2I, Point2I, Extent2I, Point2D, Box2D, SpherePoint, degrees 

31from lsst.afw.geom import makeSkyWcs, Polygon 

32from lsst.afw.table import ExposureTable 

33from lsst.afw.image import (Image, Mask, Exposure, LOCAL, PARENT, MaskPixel, VariancePixel, 

34 ImageFitsReader, MaskFitsReader, MaskedImageFitsReader, ExposureFitsReader, 

35 FilterLabel, PhotoCalib, ApCorrMap, VisitInfo, TransmissionCurve, 

36 CoaddInputs, ExposureInfo, ExposureF) 

37from lsst.afw.detection import GaussianPsf 

38from lsst.afw.cameraGeom.testUtils import DetectorWrapper 

39 

40TESTDIR = os.path.abspath(os.path.dirname(__file__)) 

41 

42 

43class FitsReaderTestCase(lsst.utils.tests.TestCase): 

44 

45 def setUp(self): 

46 self.dtypes = [np.dtype(t) for t in (np.uint16, np.int32, np.float32, np.float64)] 

47 self.bbox = Box2I(Point2I(2, 1), Extent2I(5, 7)) 

48 self.args = [ 

49 (), 

50 (Box2I(Point2I(3, 4), Extent2I(2, 1)),), 

51 (Box2I(Point2I(3, 4), Extent2I(2, 1)), PARENT), 

52 (Box2I(Point2I(1, 0), Extent2I(3, 2)), LOCAL), 

53 ] 

54 

55 def testImageFitsReader(self): 

56 for n, dtypeIn in enumerate(self.dtypes): 

57 with self.subTest(dtypeIn=dtypeIn): 

58 imageIn = Image(self.bbox, dtype=dtypeIn) 

59 imageIn.array[:, :] = np.random.randint(low=1, high=5, size=imageIn.array.shape) 

60 with lsst.utils.tests.getTempFilePath(".fits") as fileName: 

61 imageIn.writeFits(fileName) 

62 reader = ImageFitsReader(fileName) 

63 self.assertEqual(reader.readBBox(), self.bbox) 

64 self.assertEqual(reader.readDType(), dtypeIn) 

65 self.assertEqual(reader.fileName, fileName) 

66 for args in self.args: 

67 with self.subTest(args=args): 

68 array1 = reader.readArray(*args) 

69 image1 = reader.read(*args) 

70 subIn = imageIn.subset(*args) if args else imageIn 

71 self.assertEqual(dtypeIn, array1.dtype) 

72 self.assertTrue(np.all(subIn.array == array1)) 

73 self.assertEqual(subIn.getXY0(), reader.readXY0(*args)) 

74 self.assertImagesEqual(subIn, image1) 

75 for dtype2 in self.dtypes[n:]: 

76 for args in self.args: 

77 with self.subTest(dtype2=dtype2, args=args): 

78 subIn = imageIn.subset(*args) if args else imageIn 

79 array2 = reader.readArray(*args, dtype=dtype2) 

80 image2 = reader.read(*args, dtype=dtype2) 

81 self.assertEqual(dtype2, array2.dtype) 

82 self.assertTrue(np.all(subIn.array == array2)) 

83 self.assertEqual(subIn.getXY0(), reader.readXY0(*args)) 

84 self.assertEqual(subIn.getBBox(), image2.getBBox()) 

85 self.assertTrue(np.all(image2.array == array2)) 

86 

87 def testMaskFitsReader(self): 

88 maskIn = Mask(self.bbox, dtype=MaskPixel) 

89 maskIn.array[:, :] = np.random.randint(low=1, high=5, size=maskIn.array.shape) 

90 with lsst.utils.tests.getTempFilePath(".fits") as fileName: 

91 maskIn.writeFits(fileName) 

92 reader = MaskFitsReader(fileName) 

93 self.assertEqual(reader.readBBox(), self.bbox) 

94 self.assertEqual(reader.readDType(), MaskPixel) 

95 self.assertEqual(reader.fileName, fileName) 

96 for args in self.args: 

97 with self.subTest(args=args): 

98 array = reader.readArray(*args) 

99 mask = reader.read(*args) 

100 subIn = maskIn.subset(*args) if args else maskIn 

101 self.assertEqual(MaskPixel, array.dtype) 

102 self.assertTrue(np.all(subIn.array == array)) 

103 self.assertEqual(subIn.getXY0(), reader.readXY0(*args)) 

104 self.assertImagesEqual(subIn, mask) 

105 

106 def checkMultiPlaneReader(self, reader, objectIn, fileName, dtypesOut, compare): 

107 """Test operations common to MaskedImageFitsReader and ExposureFitsReader. 

108 

109 Parameters 

110 ---------- 

111 reader : `MaskedImageFitsReader` or `ExposureFitsReader` instance 

112 Reader object to test. 

113 objectIn : `MaskedImage` or `Exposure` 

114 Object originally saved, to compare against. 

115 fileName : `str` 

116 Name of the file the reader is reading. 

117 dtypesOut : sequence of `numpy.dype` 

118 Compatible image pixel types to try to read in. 

119 compare : callable 

120 Callable that compares objects of the same type as objectIn and 

121 asserts if they are not equal. 

122 """ 

123 dtypeIn = objectIn.image.dtype 

124 self.assertEqual(reader.readBBox(), self.bbox) 

125 self.assertEqual(reader.readImageDType(), dtypeIn) 

126 self.assertEqual(reader.readMaskDType(), MaskPixel) 

127 self.assertEqual(reader.readVarianceDType(), VariancePixel) 

128 self.assertEqual(reader.fileName, fileName) 

129 for args in self.args: 

130 with self.subTest(args=args): 

131 object1 = reader.read(*args) 

132 subIn = objectIn.subset(*args) if args else objectIn 

133 self.assertEqual(object1.image.array.dtype, dtypeIn) 

134 self.assertEqual(object1.mask.array.dtype, MaskPixel) 

135 self.assertEqual(object1.variance.array.dtype, VariancePixel) 

136 self.assertImagesEqual(subIn.image, reader.readImage(*args)) 

137 self.assertImagesEqual(subIn.mask, reader.readMask(*args)) 

138 self.assertImagesEqual(subIn.variance, reader.readVariance(*args)) 

139 compare(subIn, object1) 

140 for dtype2 in dtypesOut: 

141 with self.subTest(dtype2=dtype2, args=args): 

142 object2 = reader.read(*args, dtype=dtype2) 

143 image2 = reader.readImage(*args, dtype=dtype2) 

144 self.assertEqual(object2.image.array.dtype, dtype2) 

145 self.assertEqual(object2.mask.array.dtype, MaskPixel) 

146 self.assertEqual(object2.variance.array.dtype, VariancePixel) 

147 self.assertImagesEqual(subIn.image, Image(image2, deep=True, dtype=dtypeIn)) 

148 self.assertImagesEqual(image2, object2.image) 

149 compare(subIn, object2) 

150 

151 def checkMaskedImageFitsReader(self, exposureIn, fileName, dtypesOut): 

152 """Test MaskedImageFitsReader. 

153 

154 Parameters 

155 ---------- 

156 exposureIn : `Exposure` 

157 Object originally saved, to compare against. 

158 fileName : `str` 

159 Name of the file the reader is reading. 

160 dtypesOut : sequence of `numpy.dype` 

161 Compatible image pixel types to try to read in. 

162 """ 

163 reader = MaskedImageFitsReader(fileName) 

164 self.checkMultiPlaneReader(reader, exposureIn.maskedImage, fileName, dtypesOut, 

165 compare=self.assertMaskedImagesEqual) 

166 

167 def checkExposureFitsReader(self, exposureIn, fileName, dtypesOut): 

168 """Test ExposureFitsReader. 

169 

170 Parameters 

171 ---------- 

172 exposureIn : `Exposure` 

173 Object originally saved, to compare against. 

174 fileName : `str` 

175 Name of the file the reader is reading. 

176 dtypesOut : sequence of `numpy.dype` 

177 Compatible image pixel types to try to read in. 

178 """ 

179 reader = ExposureFitsReader(fileName) 

180 self.assertIn('EXPINFO_V', reader.readMetadata().toDict(), "metadata is automatically versioned") 

181 reader.readMetadata().remove('EXPINFO_V') 

182 # ensure EXTNAMEs can be read and make sense 

183 extnames = set(('PRIMARY', 'IMAGE', 'MASK', 'VARIANCE', 'ARCHIVE_INDEX', 

184 'Detector', 'TransformMap', 'TransformPoint2ToPoint2', 

185 'FilterLabel', 'SkyWcs', 'ApCorrMap', 'PhotoCalib', 

186 'ChebyshevBoundedField', 'CoaddInputs', 'GaussianPsf', 

187 'Polygon', 'VisitInfo')) 

188 with astropy.io.fits.open(fileName) as astropyReadFile: 

189 for hdu in astropyReadFile: 

190 self.assertIn(hdu.name, extnames) 

191 self.assertIn('EXTNAME', reader.readMetadata().toDict(), "EXTNAME is added upon writing") 

192 reader.readMetadata().remove('EXTNAME') 

193 self.assertGreaterEqual(reader.readSerializationVersion(), 0) 

194 self.assertEqual(exposureIn.info.id, reader.readExposureId()) 

195 self.assertEqual(exposureIn.getMetadata().toDict(), reader.readMetadata().toDict()) 

196 self.assertWcsAlmostEqualOverBBox(exposureIn.getWcs(), reader.readWcs(), self.bbox, 

197 maxDiffPix=0, maxDiffSky=0*degrees) 

198 self.assertWcsAlmostEqualOverBBox(exposureIn.getWcs(), 

199 reader.readComponent(ExposureInfo.KEY_WCS), 

200 self.bbox, 

201 maxDiffPix=0, maxDiffSky=0*degrees) 

202 self.assertEqual(exposureIn.getFilter(), reader.readFilter()) 

203 self.assertEqual(exposureIn.getFilter(), 

204 reader.readComponent(ExposureInfo.KEY_FILTER)) 

205 with self.assertWarns(FutureWarning): 

206 self.assertEqual(exposureIn.getFilterLabel(), reader.readFilterLabel()) 

207 self.assertEqual(exposureIn.getFilterLabel(), 

208 reader.readComponent(ExposureInfo.KEY_FILTER)) 

209 self.assertEqual(exposureIn.getPhotoCalib(), reader.readPhotoCalib()) 

210 self.assertEqual(exposureIn.getPhotoCalib(), 

211 reader.readComponent(ExposureInfo.KEY_PHOTO_CALIB)) 

212 center = exposureIn.getBBox().getCenter() 

213 self.assertImagesEqual(exposureIn.getPsf().computeImage(center), 

214 reader.readPsf().computeImage(center)) 

215 self.assertImagesEqual(exposureIn.getPsf().computeImage(center), 

216 reader.readComponent('PSF').computeImage(center)) 

217 self.assertEqual(exposureIn.getInfo().getValidPolygon(), reader.readValidPolygon()) 

218 self.assertEqual(exposureIn.getInfo().getValidPolygon(), 

219 reader.readComponent(ExposureInfo.KEY_VALID_POLYGON)) 

220 self.assertCountEqual(exposureIn.getInfo().getApCorrMap(), reader.readApCorrMap()) 

221 self.assertCountEqual(exposureIn.getInfo().getApCorrMap(), 

222 reader.readComponent(ExposureInfo.KEY_AP_CORR_MAP)) 

223 self.assertEqual(exposureIn.getInfo().getVisitInfo().getExposureTime(), 

224 reader.readVisitInfo().getExposureTime()) 

225 point = Point2D(2.3, 3.1) 

226 wavelengths = np.linspace(4000, 5000, 5) 

227 self.assertFloatsEqual(exposureIn.getInfo().getTransmissionCurve().sampleAt(point, wavelengths), 

228 reader.readTransmissionCurve().sampleAt(point, wavelengths)) 

229 # Note: readComponent(ExposureInfo.KEY_TRANSMISSION_CURVE) returns a generic Storable 

230 # rather than a TransmissionCurve object. 

231 

232 # Because we persisted the same instances, we should get back the same 

233 # instances for *archive* components, and hence equality comparisons 

234 # should work even if it just amounts to C++ pointer equality. 

235 record = reader.readCoaddInputs().ccds[0] 

236 self.assertEqual(record.getWcs(), reader.readWcs()) 

237 self.assertEqual(record.getPsf(), reader.readPsf()) 

238 self.assertEqual(record.getValidPolygon(), reader.readValidPolygon()) 

239 self.assertEqual(record.getApCorrMap(), reader.readApCorrMap()) 

240 self.assertEqual(record.getPhotoCalib(), reader.readPhotoCalib()) 

241 self.assertEqual(record.getDetector(), reader.readDetector()) 

242 self.checkMultiPlaneReader( 

243 reader, exposureIn, fileName, dtypesOut, 

244 compare=lambda a, b: self.assertMaskedImagesEqual(a.maskedImage, b.maskedImage) 

245 ) 

246 

247 def testCompressedSinglePlaneExposureFitsReader(self): 

248 """Test that a compressed single plane image can be read as exposure. 

249 """ 

250 uncompressed_file = os.path.join(TESTDIR, "data", "ticketdm26260.fits") 

251 compressed_file = os.path.join(TESTDIR, "data", "ticketdm26260.fits.fz") 

252 uncompressed = ExposureFitsReader(uncompressed_file).read() 

253 compressed = ExposureFitsReader(compressed_file).read() 

254 

255 self.assertMaskedImagesEqual(uncompressed.maskedImage, compressed.maskedImage) 

256 

257 def testMultiPlaneFitsReaders(self): 

258 """Run tests for MaskedImageFitsReader and ExposureFitsReader. 

259 """ 

260 metadata = PropertyList() 

261 metadata.add("FIVE", 5) 

262 metadata.add("SIX", 6.0) 

263 wcs = makeSkyWcs(Point2D(2.5, 3.75), SpherePoint(40.0*degrees, 50.0*degrees), 

264 np.array([[1E-5, 0.0], [0.0, -1E-5]])) 

265 calib = PhotoCalib(2.5E4) 

266 psf = GaussianPsf(21, 21, 8.0) 

267 polygon = Polygon(Box2D(self.bbox)) 

268 apCorrMap = ApCorrMap() 

269 visitInfo = VisitInfo(exposureTime=5.0) 

270 transmissionCurve = TransmissionCurve.makeIdentity() 

271 coaddInputs = CoaddInputs(ExposureTable.makeMinimalSchema(), ExposureTable.makeMinimalSchema()) 

272 detector = DetectorWrapper().detector 

273 record = coaddInputs.ccds.addNew() 

274 record.setWcs(wcs) 

275 record.setPhotoCalib(calib) 

276 record.setPsf(psf) 

277 record.setValidPolygon(polygon) 

278 record.setApCorrMap(apCorrMap) 

279 record.setVisitInfo(visitInfo) 

280 record.setTransmissionCurve(transmissionCurve) 

281 record.setDetector(detector) 

282 for n, dtypeIn in enumerate(self.dtypes): 

283 with self.subTest(dtypeIn=dtypeIn): 

284 exposureIn = Exposure(self.bbox, dtype=dtypeIn) 

285 shape = exposureIn.image.array.shape 

286 exposureIn.image.array[:, :] = np.random.randint(low=1, high=5, size=shape) 

287 exposureIn.mask.array[:, :] = np.random.randint(low=1, high=5, size=shape) 

288 exposureIn.variance.array[:, :] = np.random.randint(low=1, high=5, size=shape) 

289 exposureIn.setMetadata(metadata) 

290 exposureIn.setWcs(wcs) 

291 exposureIn.setFilter(FilterLabel(physical="test_readers_filter")) 

292 exposureIn.setPhotoCalib(calib) 

293 exposureIn.setPsf(psf) 

294 exposureIn.getInfo().setValidPolygon(polygon) 

295 exposureIn.getInfo().setApCorrMap(apCorrMap) 

296 exposureIn.getInfo().setVisitInfo(visitInfo) 

297 exposureIn.getInfo().setTransmissionCurve(transmissionCurve) 

298 exposureIn.getInfo().setCoaddInputs(coaddInputs) 

299 exposureIn.setDetector(detector) 

300 with lsst.utils.tests.getTempFilePath(".fits") as fileName: 

301 exposureIn.writeFits(fileName) 

302 self.checkMaskedImageFitsReader(exposureIn, fileName, self.dtypes[n:]) 

303 self.checkExposureFitsReader(exposureIn, fileName, self.dtypes[n:]) 

304 

305 def test31035(self): 

306 """Test that illegal values in the header can be round-tripped.""" 

307 with lsst.utils.tests.getTempFilePath(".fits") as fileName: 

308 exp = ExposureF(width=100, height=100) 

309 md = exp.getMetadata() 

310 md['BORE-RA'] = 'NaN' 

311 md['BORE-DEC'] = 'NaN' 

312 md['BORE-AZ'] = 'NaN' 

313 md['BORE-ALT'] = 'NaN' 

314 md['BORE-AIRMASS'] = 'NaN' 

315 md['BORE-ROTANG'] = 'NaN' 

316 md['OBS-LONG'] = 'NaN' 

317 md['OBS-LAT'] = 'NaN' 

318 md['OBS-ELEV'] = 'NaN' 

319 md['AIRTEMP'] = 'NaN' 

320 md['AIRPRESS'] = 'NaN' 

321 md['HUMIDITY'] = 'NaN' 

322 

323 exp.writeFits(fileName) 

324 

325 _ = ExposureF.readFits(fileName) 

326 

327 

328class TestMemory(lsst.utils.tests.MemoryTestCase): 

329 pass 

330 

331 

332def setup_module(module): 

333 lsst.utils.tests.init() 

334 

335 

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

337 import sys 

338 setup_module(sys.modules[__name__]) 

339 unittest.main()