Coverage for tests/test_readers.py: 10%

227 statements  

« prev     ^ index     » next       coverage.py v7.5.1, created at 2024-05-16 03:19 -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, MaskedImage, 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 testMaskedImageFitsReader(self): 

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

108 with self.subTest(dtypeIn=dtypeIn): 

109 maskedImageIn = MaskedImage(self.bbox, dtype=dtypeIn) 

110 maskedImageIn.image.array[:, :] = np.random.randint(low=1, high=5, 

111 size=maskedImageIn.image.array.shape 

112 ) 

113 maskedImageIn.mask.array[:, :] = np.random.randint(low=1, high=5, 

114 size=maskedImageIn.mask.array.shape 

115 ) 

116 maskedImageIn.variance.array[:, :] = np.random.randint(low=1, high=5, 

117 size=maskedImageIn.variance.array.shape 

118 ) 

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

120 maskedImageIn.writeFits(fileName) 

121 reader = MaskedImageFitsReader(fileName) 

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

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

124 self.assertEqual(reader.fileName, fileName) 

125 self.checkMultiPlaneReader(reader, maskedImageIn, fileName, self.dtypes[n:], 

126 compare=self.assertMaskedImagesEqual) 

127 self.checkMaskedImageFitsReader(maskedImageIn, fileName, self.dtypes[n:]) 

128 

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

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

131 

132 Parameters 

133 ---------- 

134 reader : `MaskedImageFitsReader` or `ExposureFitsReader` instance 

135 Reader object to test. 

136 objectIn : `MaskedImage` or `Exposure` 

137 Object originally saved, to compare against. 

138 fileName : `str` 

139 Name of the file the reader is reading. 

140 dtypesOut : sequence of `numpy.dype` 

141 Compatible image pixel types to try to read in. 

142 compare : callable 

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

144 asserts if they are not equal. 

145 """ 

146 dtypeIn = objectIn.image.dtype 

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

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

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

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

151 self.assertEqual(reader.fileName, fileName) 

152 for args in self.args: 

153 with self.subTest(args=args): 

154 object1 = reader.read(*args) 

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

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

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

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

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

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

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

162 compare(subIn, object1) 

163 for dtype2 in dtypesOut: 

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

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

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

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

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

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

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

171 self.assertImagesEqual(image2, object2.image) 

172 compare(subIn, object2) 

173 

174 def checkMaskedImageFitsReader(self, maskedImageIn, fileName, dtypesOut): 

175 """Test MaskedImageFitsReader. 

176 

177 Parameters 

178 ---------- 

179 maskedImageIn : `MaskedImage` 

180 Object originally saved, to compare against. 

181 fileName : `str` 

182 Name of the file the reader is reading. 

183 dtypesOut : sequence of `numpy.dype` 

184 Compatible image pixel types to try to read in. 

185 """ 

186 reader = MaskedImageFitsReader(fileName) 

187 self.checkMultiPlaneReader(reader, maskedImageIn, fileName, dtypesOut, 

188 compare=self.assertMaskedImagesEqual) 

189 

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

191 """Test ExposureFitsReader. 

192 

193 Parameters 

194 ---------- 

195 exposureIn : `Exposure` 

196 Object originally saved, to compare against. 

197 fileName : `str` 

198 Name of the file the reader is reading. 

199 dtypesOut : sequence of `numpy.dype` 

200 Compatible image pixel types to try to read in. 

201 """ 

202 reader = ExposureFitsReader(fileName) 

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

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

205 # ensure EXTNAMEs can be read and make sense 

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

207 'Detector', 'TransformMap', 'TransformPoint2ToPoint2', 

208 'FilterLabel', 'SkyWcs', 'ApCorrMap', 'PhotoCalib', 

209 'ChebyshevBoundedField', 'CoaddInputs', 'GaussianPsf', 

210 'Polygon', 'VisitInfo')) 

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

212 for hdu in astropyReadFile: 

213 self.assertIn(hdu.name, extnames) 

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

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

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

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

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

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

220 maxDiffPix=0, maxDiffSky=0*degrees) 

221 self.assertWcsAlmostEqualOverBBox(exposureIn.getWcs(), 

222 reader.readComponent(ExposureInfo.KEY_WCS), 

223 self.bbox, 

224 maxDiffPix=0, maxDiffSky=0*degrees) 

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

226 self.assertEqual(exposureIn.getFilter(), 

227 reader.readComponent(ExposureInfo.KEY_FILTER)) 

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

229 self.assertEqual(exposureIn.getPhotoCalib(), 

230 reader.readComponent(ExposureInfo.KEY_PHOTO_CALIB)) 

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

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

233 reader.readPsf().computeImage(center)) 

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

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

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

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

238 reader.readComponent(ExposureInfo.KEY_VALID_POLYGON)) 

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

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

241 reader.readComponent(ExposureInfo.KEY_AP_CORR_MAP)) 

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

243 reader.readVisitInfo().getExposureTime()) 

244 point = Point2D(2.3, 3.1) 

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

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

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

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

249 # rather than a TransmissionCurve object. 

250 

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

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

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

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

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

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

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

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

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

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

261 self.checkMultiPlaneReader( 

262 reader, exposureIn, fileName, dtypesOut, 

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

264 ) 

265 

266 def testCompressedSinglePlaneExposureFitsReader(self): 

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

268 """ 

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

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

271 uncompressed = ExposureFitsReader(uncompressed_file).read() 

272 compressed = ExposureFitsReader(compressed_file).read() 

273 

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

275 

276 def testMultiPlaneFitsReaders(self): 

277 """Run tests for MaskedImageFitsReader and ExposureFitsReader. 

278 """ 

279 metadata = PropertyList() 

280 metadata.add("FIVE", 5) 

281 metadata.add("SIX", 6.0) 

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

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

284 calib = PhotoCalib(2.5E4) 

285 psf = GaussianPsf(21, 21, 8.0) 

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

287 apCorrMap = ApCorrMap() 

288 visitInfo = VisitInfo(exposureTime=5.0) 

289 transmissionCurve = TransmissionCurve.makeIdentity() 

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

291 detector = DetectorWrapper().detector 

292 record = coaddInputs.ccds.addNew() 

293 record.setWcs(wcs) 

294 record.setPhotoCalib(calib) 

295 record.setPsf(psf) 

296 record.setValidPolygon(polygon) 

297 record.setApCorrMap(apCorrMap) 

298 record.setVisitInfo(visitInfo) 

299 record.setTransmissionCurve(transmissionCurve) 

300 record.setDetector(detector) 

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

302 with self.subTest(dtypeIn=dtypeIn): 

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

304 shape = exposureIn.image.array.shape 

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

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

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

308 exposureIn.setMetadata(metadata) 

309 exposureIn.setWcs(wcs) 

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

311 exposureIn.setPhotoCalib(calib) 

312 exposureIn.setPsf(psf) 

313 exposureIn.getInfo().setValidPolygon(polygon) 

314 exposureIn.getInfo().setApCorrMap(apCorrMap) 

315 exposureIn.getInfo().setVisitInfo(visitInfo) 

316 exposureIn.getInfo().setTransmissionCurve(transmissionCurve) 

317 exposureIn.getInfo().setCoaddInputs(coaddInputs) 

318 exposureIn.setDetector(detector) 

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

320 exposureIn.writeFits(fileName) 

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

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

323 

324 def test31035(self): 

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

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

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

328 md = exp.getMetadata() 

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

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

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

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

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

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

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

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

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

338 md['AIRTEMP'] = 'NaN' 

339 md['AIRPRESS'] = 'NaN' 

340 md['HUMIDITY'] = 'NaN' 

341 

342 exp.writeFits(fileName) 

343 

344 _ = ExposureF.readFits(fileName) 

345 

346 

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

348 pass 

349 

350 

351def setup_module(module): 

352 lsst.utils.tests.init() 

353 

354 

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

356 import sys 

357 setup_module(sys.modules[__name__]) 

358 unittest.main()