Coverage for tests/test_makeLimitedFitsHeader.py: 20%

68 statements  

« prev     ^ index     » next       coverage.py v6.4.2, created at 2022-08-01 01:19 -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# 

22 

23import unittest 

24import numpy as np 

25 

26import lsst.utils.tests 

27from lsst.daf.base import PropertyList 

28from lsst.afw.fits import makeLimitedFitsHeader 

29 

30 

31class MakeLimitedFitsHeaderTestCase(lsst.utils.tests.TestCase): 

32 

33 def assertHeadersEqual(self, header, expectedHeader, rtol=np.finfo(np.float64).resolution): 

34 """Compare 80 characters at a time 

35 

36 Floating point values are extracted from the FITS card and compared 

37 as floating point numbers rather than as strings. 

38 

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 

71 

72 def checkExcludeNames(self, metadata, expectedLines): 

73 """Check that makeLimitedFitsHeader properly excludes specified names 

74 """ 

75 names = metadata.names() 

76 

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)) 

80 

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) 

86 

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) 

108 

109 header = makeLimitedFitsHeader(metadata) 

110 

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) 

124 

125 self.assertHeadersEqual(header, expectedHeader) 

126 

127 self.checkExcludeNames(metadata, expectedLines) 

128 

129 def testSinglePrecision(self): 

130 """Check that single precision floats do work""" 

131 metadata = PropertyList() 

132 

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) 

138 

139 header = makeLimitedFitsHeader(metadata) 

140 

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) 

148 

149 self.assertHeadersEqual(header, expectedHeader, rtol=np.finfo(np.float32).resolution) 

150 

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) 

165 

166 header = makeLimitedFitsHeader(metadata) 

167 

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) 

175 

176 self.assertHeadersEqual(header, expectedHeader) 

177 

178 self.checkExcludeNames(metadata, expectedLines) 

179 

180 

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

182 pass 

183 

184 

185def setup_module(module): 

186 lsst.utils.tests.init() 

187 

188 

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()