Coverage for tests/test_readers.py: 11%
212 statements
« prev ^ index » next coverage.py v6.5.0, created at 2023-03-29 02:27 -0700
« prev ^ index » next coverage.py v6.5.0, created at 2023-03-29 02:27 -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, 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 checkMultiPlaneReader(self, reader, objectIn, fileName, dtypesOut, compare):
107 """Test operations common to MaskedImageFitsReader and ExposureFitsReader.
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)
151 def checkMaskedImageFitsReader(self, exposureIn, fileName, dtypesOut):
152 """Test MaskedImageFitsReader.
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)
167 def checkExposureFitsReader(self, exposureIn, fileName, dtypesOut):
168 """Test ExposureFitsReader.
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 self.assertEqual(exposureIn.getPhotoCalib(), reader.readPhotoCalib())
206 self.assertEqual(exposureIn.getPhotoCalib(),
207 reader.readComponent(ExposureInfo.KEY_PHOTO_CALIB))
208 center = exposureIn.getBBox().getCenter()
209 self.assertImagesEqual(exposureIn.getPsf().computeImage(center),
210 reader.readPsf().computeImage(center))
211 self.assertImagesEqual(exposureIn.getPsf().computeImage(center),
212 reader.readComponent('PSF').computeImage(center))
213 self.assertEqual(exposureIn.getInfo().getValidPolygon(), reader.readValidPolygon())
214 self.assertEqual(exposureIn.getInfo().getValidPolygon(),
215 reader.readComponent(ExposureInfo.KEY_VALID_POLYGON))
216 self.assertCountEqual(exposureIn.getInfo().getApCorrMap(), reader.readApCorrMap())
217 self.assertCountEqual(exposureIn.getInfo().getApCorrMap(),
218 reader.readComponent(ExposureInfo.KEY_AP_CORR_MAP))
219 self.assertEqual(exposureIn.getInfo().getVisitInfo().getExposureTime(),
220 reader.readVisitInfo().getExposureTime())
221 point = Point2D(2.3, 3.1)
222 wavelengths = np.linspace(4000, 5000, 5)
223 self.assertFloatsEqual(exposureIn.getInfo().getTransmissionCurve().sampleAt(point, wavelengths),
224 reader.readTransmissionCurve().sampleAt(point, wavelengths))
225 # Note: readComponent(ExposureInfo.KEY_TRANSMISSION_CURVE) returns a generic Storable
226 # rather than a TransmissionCurve object.
228 # Because we persisted the same instances, we should get back the same
229 # instances for *archive* components, and hence equality comparisons
230 # should work even if it just amounts to C++ pointer equality.
231 record = reader.readCoaddInputs().ccds[0]
232 self.assertEqual(record.getWcs(), reader.readWcs())
233 self.assertEqual(record.getPsf(), reader.readPsf())
234 self.assertEqual(record.getValidPolygon(), reader.readValidPolygon())
235 self.assertEqual(record.getApCorrMap(), reader.readApCorrMap())
236 self.assertEqual(record.getPhotoCalib(), reader.readPhotoCalib())
237 self.assertEqual(record.getDetector(), reader.readDetector())
238 self.checkMultiPlaneReader(
239 reader, exposureIn, fileName, dtypesOut,
240 compare=lambda a, b: self.assertMaskedImagesEqual(a.maskedImage, b.maskedImage)
241 )
243 def testCompressedSinglePlaneExposureFitsReader(self):
244 """Test that a compressed single plane image can be read as exposure.
245 """
246 uncompressed_file = os.path.join(TESTDIR, "data", "ticketdm26260.fits")
247 compressed_file = os.path.join(TESTDIR, "data", "ticketdm26260.fits.fz")
248 uncompressed = ExposureFitsReader(uncompressed_file).read()
249 compressed = ExposureFitsReader(compressed_file).read()
251 self.assertMaskedImagesEqual(uncompressed.maskedImage, compressed.maskedImage)
253 def testMultiPlaneFitsReaders(self):
254 """Run tests for MaskedImageFitsReader and ExposureFitsReader.
255 """
256 metadata = PropertyList()
257 metadata.add("FIVE", 5)
258 metadata.add("SIX", 6.0)
259 wcs = makeSkyWcs(Point2D(2.5, 3.75), SpherePoint(40.0*degrees, 50.0*degrees),
260 np.array([[1E-5, 0.0], [0.0, -1E-5]]))
261 calib = PhotoCalib(2.5E4)
262 psf = GaussianPsf(21, 21, 8.0)
263 polygon = Polygon(Box2D(self.bbox))
264 apCorrMap = ApCorrMap()
265 visitInfo = VisitInfo(exposureTime=5.0)
266 transmissionCurve = TransmissionCurve.makeIdentity()
267 coaddInputs = CoaddInputs(ExposureTable.makeMinimalSchema(), ExposureTable.makeMinimalSchema())
268 detector = DetectorWrapper().detector
269 record = coaddInputs.ccds.addNew()
270 record.setWcs(wcs)
271 record.setPhotoCalib(calib)
272 record.setPsf(psf)
273 record.setValidPolygon(polygon)
274 record.setApCorrMap(apCorrMap)
275 record.setVisitInfo(visitInfo)
276 record.setTransmissionCurve(transmissionCurve)
277 record.setDetector(detector)
278 for n, dtypeIn in enumerate(self.dtypes):
279 with self.subTest(dtypeIn=dtypeIn):
280 exposureIn = Exposure(self.bbox, dtype=dtypeIn)
281 shape = exposureIn.image.array.shape
282 exposureIn.image.array[:, :] = np.random.randint(low=1, high=5, size=shape)
283 exposureIn.mask.array[:, :] = np.random.randint(low=1, high=5, size=shape)
284 exposureIn.variance.array[:, :] = np.random.randint(low=1, high=5, size=shape)
285 exposureIn.setMetadata(metadata)
286 exposureIn.setWcs(wcs)
287 exposureIn.setFilter(FilterLabel(physical="test_readers_filter"))
288 exposureIn.setPhotoCalib(calib)
289 exposureIn.setPsf(psf)
290 exposureIn.getInfo().setValidPolygon(polygon)
291 exposureIn.getInfo().setApCorrMap(apCorrMap)
292 exposureIn.getInfo().setVisitInfo(visitInfo)
293 exposureIn.getInfo().setTransmissionCurve(transmissionCurve)
294 exposureIn.getInfo().setCoaddInputs(coaddInputs)
295 exposureIn.setDetector(detector)
296 with lsst.utils.tests.getTempFilePath(".fits") as fileName:
297 exposureIn.writeFits(fileName)
298 self.checkMaskedImageFitsReader(exposureIn, fileName, self.dtypes[n:])
299 self.checkExposureFitsReader(exposureIn, fileName, self.dtypes[n:])
301 def test31035(self):
302 """Test that illegal values in the header can be round-tripped."""
303 with lsst.utils.tests.getTempFilePath(".fits") as fileName:
304 exp = ExposureF(width=100, height=100)
305 md = exp.getMetadata()
306 md['BORE-RA'] = 'NaN'
307 md['BORE-DEC'] = 'NaN'
308 md['BORE-AZ'] = 'NaN'
309 md['BORE-ALT'] = 'NaN'
310 md['BORE-AIRMASS'] = 'NaN'
311 md['BORE-ROTANG'] = 'NaN'
312 md['OBS-LONG'] = 'NaN'
313 md['OBS-LAT'] = 'NaN'
314 md['OBS-ELEV'] = 'NaN'
315 md['AIRTEMP'] = 'NaN'
316 md['AIRPRESS'] = 'NaN'
317 md['HUMIDITY'] = 'NaN'
319 exp.writeFits(fileName)
321 _ = ExposureF.readFits(fileName)
324class TestMemory(lsst.utils.tests.MemoryTestCase):
325 pass
328def setup_module(module):
329 lsst.utils.tests.init()
332if __name__ == "__main__": 332 ↛ 333line 332 didn't jump to line 333, because the condition on line 332 was never true
333 import sys
334 setup_module(sys.modules[__name__])
335 unittest.main()