Coverage for tests/test_readers.py: 12%
216 statements
« prev ^ index » next coverage.py v6.4, created at 2022-06-02 03:42 -0700
« prev ^ index » next coverage.py v6.4, created at 2022-06-02 03:42 -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 Filter, FilterLabel, PhotoCalib, ApCorrMap, VisitInfo, TransmissionCurve,
36 CoaddInputs, ExposureInfo, ExposureF)
37from lsst.afw.image.utils import defineFilter
38from lsst.afw.detection import GaussianPsf
39from lsst.afw.cameraGeom.testUtils import DetectorWrapper
41TESTDIR = os.path.abspath(os.path.dirname(__file__))
44class FitsReaderTestCase(lsst.utils.tests.TestCase):
46 def setUp(self):
47 self.dtypes = [np.dtype(t) for t in (np.uint16, np.int32, np.float32, np.float64)]
48 self.bbox = Box2I(Point2I(2, 1), Extent2I(5, 7))
49 self.args = [
50 (),
51 (Box2I(Point2I(3, 4), Extent2I(2, 1)),),
52 (Box2I(Point2I(3, 4), Extent2I(2, 1)), PARENT),
53 (Box2I(Point2I(1, 0), Extent2I(3, 2)), LOCAL),
54 ]
56 def testImageFitsReader(self):
57 for n, dtypeIn in enumerate(self.dtypes):
58 with self.subTest(dtypeIn=dtypeIn):
59 imageIn = Image(self.bbox, dtype=dtypeIn)
60 imageIn.array[:, :] = np.random.randint(low=1, high=5, size=imageIn.array.shape)
61 with lsst.utils.tests.getTempFilePath(".fits") as fileName:
62 imageIn.writeFits(fileName)
63 reader = ImageFitsReader(fileName)
64 self.assertEqual(reader.readBBox(), self.bbox)
65 self.assertEqual(reader.readDType(), dtypeIn)
66 self.assertEqual(reader.fileName, fileName)
67 for args in self.args:
68 with self.subTest(args=args):
69 array1 = reader.readArray(*args)
70 image1 = reader.read(*args)
71 subIn = imageIn.subset(*args) if args else imageIn
72 self.assertEqual(dtypeIn, array1.dtype)
73 self.assertTrue(np.all(subIn.array == array1))
74 self.assertEqual(subIn.getXY0(), reader.readXY0(*args))
75 self.assertImagesEqual(subIn, image1)
76 for dtype2 in self.dtypes[n:]:
77 for args in self.args:
78 with self.subTest(dtype2=dtype2, args=args):
79 subIn = imageIn.subset(*args) if args else imageIn
80 array2 = reader.readArray(*args, dtype=dtype2)
81 image2 = reader.read(*args, dtype=dtype2)
82 self.assertEqual(dtype2, array2.dtype)
83 self.assertTrue(np.all(subIn.array == array2))
84 self.assertEqual(subIn.getXY0(), reader.readXY0(*args))
85 self.assertEqual(subIn.getBBox(), image2.getBBox())
86 self.assertTrue(np.all(image2.array == array2))
88 def testMaskFitsReader(self):
89 maskIn = Mask(self.bbox, dtype=MaskPixel)
90 maskIn.array[:, :] = np.random.randint(low=1, high=5, size=maskIn.array.shape)
91 with lsst.utils.tests.getTempFilePath(".fits") as fileName:
92 maskIn.writeFits(fileName)
93 reader = MaskFitsReader(fileName)
94 self.assertEqual(reader.readBBox(), self.bbox)
95 self.assertEqual(reader.readDType(), MaskPixel)
96 self.assertEqual(reader.fileName, fileName)
97 for args in self.args:
98 with self.subTest(args=args):
99 array = reader.readArray(*args)
100 mask = reader.read(*args)
101 subIn = maskIn.subset(*args) if args else maskIn
102 self.assertEqual(MaskPixel, array.dtype)
103 self.assertTrue(np.all(subIn.array == array))
104 self.assertEqual(subIn.getXY0(), reader.readXY0(*args))
105 self.assertImagesEqual(subIn, mask)
107 def checkMultiPlaneReader(self, reader, objectIn, fileName, dtypesOut, compare):
108 """Test operations common to MaskedImageFitsReader and ExposureFitsReader.
110 Parameters
111 ----------
112 reader : `MaskedImageFitsReader` or `ExposureFitsReader` instance
113 Reader object to test.
114 objectIn : `MaskedImage` or `Exposure`
115 Object originally saved, to compare against.
116 fileName : `str`
117 Name of the file the reader is reading.
118 dtypesOut : sequence of `numpy.dype`
119 Compatible image pixel types to try to read in.
120 compare : callable
121 Callable that compares objects of the same type as objectIn and
122 asserts if they are not equal.
123 """
124 dtypeIn = objectIn.image.dtype
125 self.assertEqual(reader.readBBox(), self.bbox)
126 self.assertEqual(reader.readImageDType(), dtypeIn)
127 self.assertEqual(reader.readMaskDType(), MaskPixel)
128 self.assertEqual(reader.readVarianceDType(), VariancePixel)
129 self.assertEqual(reader.fileName, fileName)
130 for args in self.args:
131 with self.subTest(args=args):
132 object1 = reader.read(*args)
133 subIn = objectIn.subset(*args) if args else objectIn
134 self.assertEqual(object1.image.array.dtype, dtypeIn)
135 self.assertEqual(object1.mask.array.dtype, MaskPixel)
136 self.assertEqual(object1.variance.array.dtype, VariancePixel)
137 self.assertImagesEqual(subIn.image, reader.readImage(*args))
138 self.assertImagesEqual(subIn.mask, reader.readMask(*args))
139 self.assertImagesEqual(subIn.variance, reader.readVariance(*args))
140 compare(subIn, object1)
141 for dtype2 in dtypesOut:
142 with self.subTest(dtype2=dtype2, args=args):
143 object2 = reader.read(*args, dtype=dtype2)
144 image2 = reader.readImage(*args, dtype=dtype2)
145 self.assertEqual(object2.image.array.dtype, dtype2)
146 self.assertEqual(object2.mask.array.dtype, MaskPixel)
147 self.assertEqual(object2.variance.array.dtype, VariancePixel)
148 self.assertImagesEqual(subIn.image, Image(image2, deep=True, dtype=dtypeIn))
149 self.assertImagesEqual(image2, object2.image)
150 compare(subIn, object2)
152 def checkMaskedImageFitsReader(self, exposureIn, fileName, dtypesOut):
153 """Test MaskedImageFitsReader.
155 Parameters
156 ----------
157 exposureIn : `Exposure`
158 Object originally saved, to compare against.
159 fileName : `str`
160 Name of the file the reader is reading.
161 dtypesOut : sequence of `numpy.dype`
162 Compatible image pixel types to try to read in.
163 """
164 reader = MaskedImageFitsReader(fileName)
165 self.checkMultiPlaneReader(reader, exposureIn.maskedImage, fileName, dtypesOut,
166 compare=self.assertMaskedImagesEqual)
168 def checkExposureFitsReader(self, exposureIn, fileName, dtypesOut):
169 """Test ExposureFitsReader.
171 Parameters
172 ----------
173 exposureIn : `Exposure`
174 Object originally saved, to compare against.
175 fileName : `str`
176 Name of the file the reader is reading.
177 dtypesOut : sequence of `numpy.dype`
178 Compatible image pixel types to try to read in.
179 """
180 reader = ExposureFitsReader(fileName)
181 self.assertIn('EXPINFO_V', reader.readMetadata().toDict(), "metadata is automatically versioned")
182 reader.readMetadata().remove('EXPINFO_V')
183 # ensure EXTNAMEs can be read and make sense
184 extnames = set(('PRIMARY', 'IMAGE', 'MASK', 'VARIANCE', 'ARCHIVE_INDEX',
185 'Detector', 'TransformMap', 'TransformPoint2ToPoint2',
186 'FilterLabel', 'SkyWcs', 'ApCorrMap', 'PhotoCalib',
187 'ChebyshevBoundedField', 'CoaddInputs', 'GaussianPsf',
188 'Polygon', 'VisitInfo'))
189 with astropy.io.fits.open(fileName) as astropyReadFile:
190 for hdu in astropyReadFile:
191 self.assertIn(hdu.name, extnames)
192 self.assertIn('EXTNAME', reader.readMetadata().toDict(), "EXTNAME is added upon writing")
193 reader.readMetadata().remove('EXTNAME')
194 self.assertGreaterEqual(reader.readSerializationVersion(), 0)
195 self.assertEqual(exposureIn.info.id, reader.readExposureId())
196 self.assertEqual(exposureIn.getMetadata().toDict(), reader.readMetadata().toDict())
197 self.assertWcsAlmostEqualOverBBox(exposureIn.getWcs(), reader.readWcs(), self.bbox,
198 maxDiffPix=0, maxDiffSky=0*degrees)
199 self.assertWcsAlmostEqualOverBBox(exposureIn.getWcs(),
200 reader.readComponent(ExposureInfo.KEY_WCS),
201 self.bbox,
202 maxDiffPix=0, maxDiffSky=0*degrees)
203 self.assertEqual(exposureIn.getFilter(), reader.readFilter())
204 self.assertEqual(exposureIn.getFilterLabel(), reader.readFilterLabel())
205 self.assertEqual(exposureIn.getFilterLabel(),
206 reader.readComponent(ExposureInfo.KEY_FILTER))
207 self.assertEqual(exposureIn.getPhotoCalib(), reader.readPhotoCalib())
208 self.assertEqual(exposureIn.getPhotoCalib(),
209 reader.readComponent(ExposureInfo.KEY_PHOTO_CALIB))
210 center = exposureIn.getBBox().getCenter()
211 self.assertImagesEqual(exposureIn.getPsf().computeImage(center),
212 reader.readPsf().computeImage(center))
213 self.assertImagesEqual(exposureIn.getPsf().computeImage(center),
214 reader.readComponent('PSF').computeImage(center))
215 self.assertEqual(exposureIn.getInfo().getValidPolygon(), reader.readValidPolygon())
216 self.assertEqual(exposureIn.getInfo().getValidPolygon(),
217 reader.readComponent(ExposureInfo.KEY_VALID_POLYGON))
218 self.assertCountEqual(exposureIn.getInfo().getApCorrMap(), reader.readApCorrMap())
219 self.assertCountEqual(exposureIn.getInfo().getApCorrMap(),
220 reader.readComponent(ExposureInfo.KEY_AP_CORR_MAP))
221 self.assertEqual(exposureIn.getInfo().getVisitInfo().getExposureTime(),
222 reader.readVisitInfo().getExposureTime())
223 point = Point2D(2.3, 3.1)
224 wavelengths = np.linspace(4000, 5000, 5)
225 self.assertFloatsEqual(exposureIn.getInfo().getTransmissionCurve().sampleAt(point, wavelengths),
226 reader.readTransmissionCurve().sampleAt(point, wavelengths))
227 # Note: readComponent(ExposureInfo.KEY_TRANSMISSION_CURVE) returns a generic Storable
228 # rather than a TransmissionCurve object.
230 # Because we persisted the same instances, we should get back the same
231 # instances for *archive* components, and hence equality comparisons
232 # should work even if it just amounts to C++ pointer equality.
233 record = reader.readCoaddInputs().ccds[0]
234 self.assertEqual(record.getWcs(), reader.readWcs())
235 self.assertEqual(record.getPsf(), reader.readPsf())
236 self.assertEqual(record.getValidPolygon(), reader.readValidPolygon())
237 self.assertEqual(record.getApCorrMap(), reader.readApCorrMap())
238 self.assertEqual(record.getPhotoCalib(), reader.readPhotoCalib())
239 self.assertEqual(record.getDetector(), reader.readDetector())
240 self.checkMultiPlaneReader(
241 reader, exposureIn, fileName, dtypesOut,
242 compare=lambda a, b: self.assertMaskedImagesEqual(a.maskedImage, b.maskedImage)
243 )
245 def testCompressedSinglePlaneExposureFitsReader(self):
246 """Test that a compressed single plane image can be read as exposure.
247 """
248 uncompressed_file = os.path.join(TESTDIR, "data", "ticketdm26260.fits")
249 compressed_file = os.path.join(TESTDIR, "data", "ticketdm26260.fits.fz")
250 uncompressed = ExposureFitsReader(uncompressed_file).read()
251 compressed = ExposureFitsReader(compressed_file).read()
253 self.assertMaskedImagesEqual(uncompressed.maskedImage, compressed.maskedImage)
255 def testMultiPlaneFitsReaders(self):
256 """Run tests for MaskedImageFitsReader and ExposureFitsReader.
257 """
258 metadata = PropertyList()
259 metadata.add("FIVE", 5)
260 metadata.add("SIX", 6.0)
261 wcs = makeSkyWcs(Point2D(2.5, 3.75), SpherePoint(40.0*degrees, 50.0*degrees),
262 np.array([[1E-5, 0.0], [0.0, -1E-5]]))
263 defineFilter("test_readers_filter", lambdaEff=470.0)
264 calib = PhotoCalib(2.5E4)
265 psf = GaussianPsf(21, 21, 8.0)
266 polygon = Polygon(Box2D(self.bbox))
267 apCorrMap = ApCorrMap()
268 visitInfo = VisitInfo(exposureTime=5.0)
269 transmissionCurve = TransmissionCurve.makeIdentity()
270 coaddInputs = CoaddInputs(ExposureTable.makeMinimalSchema(), ExposureTable.makeMinimalSchema())
271 detector = DetectorWrapper().detector
272 record = coaddInputs.ccds.addNew()
273 record.setWcs(wcs)
274 record.setPhotoCalib(calib)
275 record.setPsf(psf)
276 record.setValidPolygon(polygon)
277 record.setApCorrMap(apCorrMap)
278 record.setVisitInfo(visitInfo)
279 record.setTransmissionCurve(transmissionCurve)
280 record.setDetector(detector)
281 for n, dtypeIn in enumerate(self.dtypes):
282 with self.subTest(dtypeIn=dtypeIn):
283 exposureIn = Exposure(self.bbox, dtype=dtypeIn)
284 shape = exposureIn.image.array.shape
285 exposureIn.image.array[:, :] = np.random.randint(low=1, high=5, size=shape)
286 exposureIn.mask.array[:, :] = np.random.randint(low=1, high=5, size=shape)
287 exposureIn.variance.array[:, :] = np.random.randint(low=1, high=5, size=shape)
288 exposureIn.setMetadata(metadata)
289 exposureIn.setWcs(wcs)
290 exposureIn.setFilter(Filter("test_readers_filter"))
291 exposureIn.setFilterLabel(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:])
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'
323 exp.writeFits(fileName)
325 _ = ExposureF.readFits(fileName)
328class TestMemory(lsst.utils.tests.MemoryTestCase):
329 pass
332def setup_module(module):
333 lsst.utils.tests.init()
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()