Coverage for tests/test_intrinsicZernikes.py: 24%

94 statements  

« prev     ^ index     » next       coverage.py v7.14.1, created at 2026-05-29 08:34 +0000

1# This file is part of ip_isr. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (https://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 <https://www.gnu.org/licenses/>. 

21 

22import unittest 

23import tempfile 

24 

25import numpy as np 

26from astropy.table import Table 

27import astropy.units as u 

28 

29import lsst.utils.tests 

30 

31from lsst.ip.isr import IntrinsicZernikes 

32 

33 

34class IntrinsicZernikesTestCase(lsst.utils.tests.TestCase): 

35 """Test the IntrinsicZernikes calibration class.""" 

36 

37 def setUp(self): 

38 """Create test data for intrinsic Zernikes.""" 

39 # Create test Zernike coefficients for Noll indices 4, 5, 6 

40 # (defocus, astigmatism) 

41 self.noll_indices = np.array([4, 5, 6]) 

42 

43 # Build a regular 3x3 grid of sample points 

44 x_unique = np.array([-1.0, 0.0, 1.0]) 

45 y_unique = np.array([-0.5, 0.0, 0.5]) 

46 x_grid, y_grid = np.meshgrid(x_unique, y_unique) 

47 self.field_x = x_grid.ravel() 

48 self.field_y = y_grid.ravel() 

49 

50 # Create values: shape (n_points, n_zernikes) 

51 rng = np.random.default_rng(seed=57721) 

52 self.values = rng.normal( 

53 scale=0.1, 

54 size=(len(self.field_x), len(self.noll_indices)) 

55 ) # microns 

56 

57 # Create an astropy table in the format expected by __init__ 

58 self.inputTable = Table() 

59 self.inputTable["x"] = self.field_x * u.deg 

60 self.inputTable["y"] = self.field_y * u.deg 

61 

62 # Add Zernike columns 

63 for i, noll in enumerate(self.noll_indices): 

64 self.inputTable[f"Z{noll}"] = self.values[:, i] * u.um 

65 

66 # Create the calibration object 

67 self.calib = IntrinsicZernikes(table=self.inputTable) 

68 

69 def test_initialization_with_table(self): 

70 """Test that IntrinsicZernikes initializes correctly from a table.""" 

71 np.testing.assert_array_equal(self.calib.field_x, self.field_x) 

72 np.testing.assert_array_equal(self.calib.field_y, self.field_y) 

73 np.testing.assert_array_equal(self.calib.noll_indices, self.noll_indices) 

74 np.testing.assert_array_equal(self.calib.values, self.values) 

75 self.assertIsNotNone(self.calib.interpolator) 

76 

77 def test_metadata(self): 

78 """Test that metadata is properly set.""" 

79 metadata = self.calib.getMetadata() 

80 self.assertEqual(metadata["OBSTYPE"], "INTRINSIC_ZERNIKES") 

81 self.assertEqual(metadata["INTRINSIC_ZERNIKES_SCHEMA"], "Intrinsic Zernikes") 

82 self.assertEqual(metadata["INTRINSIC_ZERNIKES_VERSION"], 1.0) 

83 

84 def test_dict_roundtrip(self): 

85 """Test round-tripping through dictionary.""" 

86 newCalib = IntrinsicZernikes.fromDict(self.calib.toDict()) 

87 self.assertEqual(newCalib, self.calib) 

88 

89 def test_table_roundtrip(self): 

90 """Test round-tripping through table.""" 

91 newCalib = IntrinsicZernikes.fromTable(self.calib.toTable()) 

92 self.assertEqual(newCalib, self.calib) 

93 

94 def test_yaml_roundtrip(self): 

95 """Test round-tripping through YAML file.""" 

96 with tempfile.TemporaryDirectory() as tempdir: 

97 import os 

98 filename = os.path.join(tempdir, "intrinsic_zernikes.yaml") 

99 

100 self.calib.writeText(filename) 

101 newCalib = IntrinsicZernikes.readText(filename) 

102 self.assertEqual(newCalib, self.calib) 

103 

104 def test_ecsv_roundtrip(self): 

105 """Test round-tripping through ECSV file.""" 

106 with tempfile.TemporaryDirectory() as tempdir: 

107 import os 

108 filename = os.path.join(tempdir, "intrinsic_zernikes.ecsv") 

109 

110 self.calib.writeText(filename) 

111 newCalib = IntrinsicZernikes.readText(filename) 

112 self.assertEqual(newCalib, self.calib) 

113 

114 def test_fits_roundtrip(self): 

115 """Test round-tripping through FITS file.""" 

116 with tempfile.TemporaryDirectory() as tempdir: 

117 import os 

118 filename = os.path.join(tempdir, "intrinsic_zernikes.fits") 

119 

120 self.calib.writeFits(filename) 

121 newCalib = IntrinsicZernikes.readFits(filename) 

122 self.assertEqual(newCalib, self.calib) 

123 

124 def test_fromDict_wrong_obstype(self): 

125 """Test that fromDict raises error for wrong OBSTYPE.""" 

126 outDict = self.calib.toDict() 

127 outDict["metadata"]["OBSTYPE"] = "WRONG_TYPE" 

128 

129 with self.assertRaises(RuntimeError) as context: 

130 IntrinsicZernikes.fromDict(outDict) 

131 

132 self.assertIn("Incorrect intrinsic zernikes supplied", str(context.exception)) 

133 self.assertIn("INTRINSIC_ZERNIKES", str(context.exception)) 

134 self.assertIn("WRONG_TYPE", str(context.exception)) 

135 

136 def test_getIntrinsicZernikes(self): 

137 """Test interpolation of Zernike coefficients.""" 

138 # Test at a grid point 

139 field_x_test = 0.0 

140 field_y_test = 0.0 

141 

142 zernikes = self.calib.getIntrinsicZernikes(field_x_test, field_y_test) 

143 

144 # In this case, we're on a grid point 

145 center_idx = np.flatnonzero((self.field_x == 0.0) & (self.field_y == 0.0))[0] 

146 self.assertFloatsEqual(zernikes, self.values[center_idx, :]) 

147 

148 # Test with specific Noll indices 

149 zernikes_subset = self.calib.getIntrinsicZernikes( 

150 field_x_test, field_y_test, 

151 noll_indices=[4] 

152 ) 

153 self.assertFloatsEqual(zernikes_subset, zernikes[:, 0]) 

154 

155 def test_getIntrinsicZernikes_array(self): 

156 """Test interpolation with array inputs.""" 

157 field_x_test = np.array([0.0, 0.5]) 

158 field_y_test = np.array([0.0, 0.25]) 

159 

160 zernikes = self.calib.getIntrinsicZernikes(field_x_test, field_y_test) 

161 z0 = self.calib.getIntrinsicZernikes(field_x_test[0], field_y_test[0]) 

162 z1 = self.calib.getIntrinsicZernikes(field_x_test[1], field_y_test[1]) 

163 np.testing.assert_array_almost_equal(zernikes[[0]], z0) 

164 np.testing.assert_array_almost_equal(zernikes[[1]], z1) 

165 

166 # Should return shape (n_points, n_zernikes) 

167 self.assertEqual(zernikes.shape, (2, len(self.noll_indices))) 

168 

169 

170class MemoryTester(lsst.utils.tests.MemoryTestCase): 

171 pass 

172 

173 

174def setup_module(module): 

175 lsst.utils.tests.init() 

176 

177 

178if __name__ == "__main__": 178 ↛ 179line 178 didn't jump to line 179 because the condition on line 178 was never true

179 import sys 

180 setup_module(sys.modules[__name__]) 

181 unittest.main()