Coverage for tests/test_readers.py: 10%
227 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-19 04:04 -0700
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-19 04:04 -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/>.
22import unittest
24import os
25import numpy as np
26import astropy.io.fits
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
40TESTDIR = os.path.abspath(os.path.dirname(__file__))
43class FitsReaderTestCase(lsst.utils.tests.TestCase):
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 ]
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))
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)
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:])
129 def checkMultiPlaneReader(self, reader, objectIn, fileName, dtypesOut, compare):
130 """Test operations common to MaskedImageFitsReader and ExposureFitsReader.
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)
174 def checkMaskedImageFitsReader(self, maskedImageIn, fileName, dtypesOut):
175 """Test MaskedImageFitsReader.
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)
190 def checkExposureFitsReader(self, exposureIn, fileName, dtypesOut):
191 """Test ExposureFitsReader.
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.
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 )
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()
274 self.assertMaskedImagesEqual(uncompressed.maskedImage, compressed.maskedImage)
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:])
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'
342 exp.writeFits(fileName)
344 _ = ExposureF.readFits(fileName)
347class TestMemory(lsst.utils.tests.MemoryTestCase):
348 pass
351def setup_module(module):
352 lsst.utils.tests.init()
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()