Coverage for tests/test_makeLimitedFitsHeader.py: 17%
68 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-06 04:03 -0700
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-06 04:03 -0700
1#
2# LSST Data Management System
3# Copyright 2017 LSST Corporation.
4#
5# This product includes software developed by the
6# LSST Project (http://www.lsst.org/).
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the LSST License Statement and
19# the GNU General Public License along with this program. If not,
20# see <http://www.lsstcorp.org/LegalNotices/>.
21#
23import unittest
24import numpy as np
26import lsst.utils.tests
27from lsst.daf.base import PropertyList
28from lsst.afw.fits import makeLimitedFitsHeader
31class MakeLimitedFitsHeaderTestCase(lsst.utils.tests.TestCase):
33 def assertHeadersEqual(self, header, expectedHeader, rtol=np.finfo(np.float64).resolution):
34 """Compare 80 characters at a time
36 Floating point values are extracted from the FITS card and compared
37 as floating point numbers rather than as strings.
39 Parameters
40 ----------
41 header : `str`
42 FITS-style header string calculated by the test.
43 expectedHeader : `str`
44 Reference header string.
45 rtol = `float`, optional
46 Tolerance to use for floating point comparisons. This parameters
47 is passed directly to `~lsst.utils.tests.assertFloatsAlmostEqual`.
48 The default is for double precision comparison.
49 """
50 self.assertEqual(len(header), len(expectedHeader),
51 msg="Compare header lengths")
52 start = 0
53 while start < len(header):
54 end = start + 80
55 # Strip trailing whitespace to make the diff clearer
56 this = header[start:end].rstrip()
57 expected = expectedHeader[start:end].rstrip()
58 with self.subTest(this=this, expected=expected):
59 # For floating point numbers compare as numbers
60 # rather than strings
61 if "'" not in expected and ("." in expected[9:] or "E" in expected[9:]):
62 nchars = 10
63 self.assertEqual(this[:nchars], expected[:nchars],
64 msg=f"Compare first {nchars} characters of '{this}'"
65 f" with expected '{expected}'")
66 self.assertFloatsAlmostEqual(float(this[9:]), float(expected[9:]),
67 rtol=rtol)
68 else:
69 self.assertEqual(this, expected)
70 start += 80
72 def checkExcludeNames(self, metadata, expectedLines):
73 """Check that makeLimitedFitsHeader properly excludes specified names
74 """
75 names = metadata.names()
77 # skip each name in turn, then skip all names at once
78 excludeNamesList = [set([name]) for name in names]
79 excludeNamesList.append(set(names))
81 for excludeNames in excludeNamesList:
82 header = makeLimitedFitsHeader(metadata, excludeNames=excludeNames)
83 expectedHeader = "".join("%-80s" % val for val in expectedLines
84 if val[0:8].strip() not in excludeNames)
85 self.assertHeadersEqual(header, expectedHeader)
87 def testBasics(self):
88 """Check basic formatting and skipping bad values
89 """
90 metadata = PropertyList()
91 dataList = [
92 ("ABOOL", True),
93 ("AFLOAT", 1.2e25),
94 ("AFLOAT2", 1.0e30),
95 ("ANINT", -5),
96 ("AFLOATZ", 0.0), # ensure a float stays a float
97 ("INTFLOAT", -5.0),
98 ("LONGFLT", 0.0089626337538440005),
99 ("ANUNDEF", None),
100 ("LONGNAME1", 1), # name is longer than 8 characters; skip it
101 ("LONGSTR", "skip this item because the formatted value "
102 "is too long: longer than 80 characters "),
103 ("ASTRING1", "value for string"),
104 ("ANAN", float("NaN"))
105 ]
106 for name, value in dataList:
107 metadata.set(name, value)
109 header = makeLimitedFitsHeader(metadata)
111 expectedLines = [ # without padding to 80 chars
112 "ABOOL = T",
113 "AFLOAT = 1.2E+25",
114 "AFLOAT2 = 1E+30",
115 "ANINT = -5",
116 "AFLOATZ = 0.0",
117 "INTFLOAT= -5.0",
118 "LONGFLT = 0.0089626337538440005",
119 "ANUNDEF =",
120 "ASTRING1= 'value for string'",
121 "ANAN =",
122 ]
123 expectedHeader = "".join("%-80s" % val for val in expectedLines)
125 self.assertHeadersEqual(header, expectedHeader)
127 self.checkExcludeNames(metadata, expectedLines)
129 def testSinglePrecision(self):
130 """Check that single precision floats do work"""
131 metadata = PropertyList()
133 # Numeric form of single precision floats need smaller precision
134 metadata.setFloat("SINGLE", 3.14159)
135 metadata.setFloat("SINGLEI", 5.0)
136 metadata.setFloat("SINGLEE", -5.9e20)
137 metadata.setFloat("EXP", -5e10)
139 header = makeLimitedFitsHeader(metadata)
141 expectedLines = [ # without padding to 80 chars
142 "SINGLE = 3.14159",
143 "SINGLEI = 5.0",
144 "SINGLEE = -5.9E+20",
145 "EXP = -5E+10",
146 ]
147 expectedHeader = "".join("%-80s" % val for val in expectedLines)
149 self.assertHeadersEqual(header, expectedHeader, rtol=np.finfo(np.float32).resolution)
151 def testArrayValues(self):
152 """Check that only the final value is used from an array
153 """
154 metadata = PropertyList()
155 # work around DM-13232 by setting ABOOL one value at a time
156 for value in [True, True, True, False]:
157 metadata.add("ABOOL", value)
158 dataList = [
159 ("AFLOAT", [1.2e25, -5.6]),
160 ("ANINT", [-5, 752, 1052]),
161 ("ASTRING1", ["value for string", "more"]),
162 ]
163 for name, value in dataList:
164 metadata.set(name, value)
166 header = makeLimitedFitsHeader(metadata)
168 expectedLines = [ # without padding to 80 chars
169 "ABOOL = F",
170 "AFLOAT = -5.6",
171 "ANINT = 1052",
172 "ASTRING1= 'more'",
173 ]
174 expectedHeader = "".join("%-80s" % val for val in expectedLines)
176 self.assertHeadersEqual(header, expectedHeader)
178 self.checkExcludeNames(metadata, expectedLines)
181class TestMemory(lsst.utils.tests.MemoryTestCase):
182 pass
185def setup_module(module):
186 lsst.utils.tests.init()
189if __name__ == "__main__": 189 ↛ 190line 189 didn't jump to line 190, because the condition on line 189 was never true
190 import sys
191 setup_module(sys.modules[__name__])
192 unittest.main()