Coverage for tests/test_amplifier.py: 15%

111 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-19 04:04 -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# (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 

22from types import SimpleNamespace 

23import unittest 

24import numpy as np 

25 

26import lsst.utils.tests 

27import lsst.geom 

28from lsst.afw.cameraGeom import Amplifier, AmplifierGeometryComparison, ReadoutCorner 

29import lsst.afw.cameraGeom.testUtils # for assert methods injected into TestCase 

30 

31 

32class AmplifierTestCase(lsst.utils.tests.TestCase): 

33 

34 def setUp(self): 

35 self.data = SimpleNamespace( 

36 name="Amp1", 

37 gain=1.2345, 

38 saturation=65535, 

39 readNoise=-0.523, 

40 linearityCoeffs=np.array([1.1, 2.2, 3.3, 4.4], dtype=float), 

41 linearityType="Polynomial", 

42 bbox=lsst.geom.Box2I(lsst.geom.Point2I(3, -2), lsst.geom.Extent2I(231, 320)), 

43 rawFlipX=True, 

44 rawFlipY=False, 

45 readoutCorner=ReadoutCorner.UL, 

46 rawBBox=lsst.geom.Box2I(lsst.geom.Point2I(-25, 2), lsst.geom.Extent2I(550, 629)), 

47 rawXYOffset=lsst.geom.Extent2I(-97, 253), 

48 rawDataBBox=lsst.geom.Box2I(lsst.geom.Point2I(-2, 29), lsst.geom.Extent2I(123, 307)), 

49 rawHorizontalOverscanBBox=lsst.geom.Box2I( 

50 lsst.geom.Point2I(150, 29), 

51 lsst.geom.Extent2I(25, 307), 

52 ), 

53 rawVerticalOverscanBBox=lsst.geom.Box2I( 

54 lsst.geom.Point2I(-2, 201), 

55 lsst.geom.Extent2I(123, 6), 

56 ), 

57 rawPrescanBBox=lsst.geom.Box2I( 

58 lsst.geom.Point2I(-20, 2), 

59 lsst.geom.Extent2I(5, 307), 

60 ), 

61 ) 

62 builder = Amplifier.Builder() 

63 builder.setBBox(self.data.bbox) 

64 builder.setName(self.data.name) 

65 builder.setGain(self.data.gain) 

66 builder.setSaturation(self.data.saturation) 

67 builder.setReadNoise(self.data.readNoise) 

68 builder.setReadoutCorner(self.data.readoutCorner) 

69 builder.setLinearityCoeffs(self.data.linearityCoeffs) 

70 builder.setLinearityType(self.data.linearityType) 

71 builder.setRawFlipX(self.data.rawFlipX) 

72 builder.setRawFlipY(self.data.rawFlipY) 

73 builder.setRawBBox(self.data.rawBBox) 

74 builder.setRawXYOffset(self.data.rawXYOffset) 

75 builder.setRawDataBBox(self.data.rawDataBBox) 

76 builder.setRawHorizontalOverscanBBox(self.data.rawHorizontalOverscanBBox) 

77 builder.setRawVerticalOverscanBBox(self.data.rawVerticalOverscanBBox) 

78 builder.setRawPrescanBBox(self.data.rawPrescanBBox) 

79 self.amplifier = builder.finish() 

80 

81 def testBasics(self): 

82 self.assertEqual(self.data.name, self.amplifier.getName()) 

83 self.assertEqual(self.data.gain, self.amplifier.getGain()) 

84 self.assertEqual(self.data.saturation, self.amplifier.getSaturation()) 

85 self.assertEqual(self.data.readNoise, self.amplifier.getReadNoise()) 

86 self.assertEqual(self.data.readoutCorner, self.amplifier.getReadoutCorner()) 

87 self.assertEqual(list(self.data.linearityCoeffs), list(self.amplifier.getLinearityCoeffs())) 

88 self.assertEqual(self.data.linearityType, self.amplifier.getLinearityType()) 

89 self.assertEqual(self.data.bbox, self.amplifier.getBBox()) 

90 self.assertEqual(self.data.rawBBox, self.amplifier.getRawBBox()) 

91 self.assertEqual(self.data.rawDataBBox, self.amplifier.getRawDataBBox()) 

92 self.assertEqual(self.data.rawHorizontalOverscanBBox, self.amplifier.getRawHorizontalOverscanBBox()) 

93 self.assertEqual(self.data.rawVerticalOverscanBBox, self.amplifier.getRawVerticalOverscanBBox()) 

94 self.assertEqual(self.data.rawPrescanBBox, self.amplifier.getRawPrescanBBox()) 

95 self.assertEqual(self.data.rawHorizontalOverscanBBox, self.amplifier.getRawSerialOverscanBBox()) 

96 self.assertEqual(self.data.rawVerticalOverscanBBox, self.amplifier.getRawParallelOverscanBBox()) 

97 self.assertEqual(self.data.rawPrescanBBox, self.amplifier.getRawSerialPrescanBBox()) 

98 self.assertEqual(self.data.rawPrescanBBox, self.amplifier.getRawHorizontalPrescanBBox()) 

99 self.assertEqual(self.data.rawFlipX, self.amplifier.getRawFlipX()) 

100 self.assertEqual(self.data.rawFlipY, self.amplifier.getRawFlipY()) 

101 self.assertEqual(self.data.rawXYOffset, self.amplifier.getRawXYOffset()) 

102 

103 # Test get/set methods for overscan/prescan alias names. 

104 # Change slightly, don't care about contiguity, make smaller. 

105 newHorizontalOverscanBBox = lsst.geom.Box2I( 

106 lsst.geom.Point2I(150, 29), lsst.geom.Extent2I(25, 306)) 

107 newVerticalOverscanBBox = lsst.geom.Box2I( 

108 lsst.geom.Point2I(-2, 201), lsst.geom.Extent2I(123, 5)) 

109 newPrescanBBox = lsst.geom.Box2I( 

110 lsst.geom.Point2I(-20, 2), lsst.geom.Extent2I(4, 306)) 

111 

112 builder = self.amplifier.rebuild() 

113 builder.setRawSerialOverscanBBox(newHorizontalOverscanBBox) 

114 builder.setRawParallelOverscanBBox(newVerticalOverscanBBox) 

115 builder.setRawSerialPrescanBBox(newPrescanBBox) 

116 amplifier = builder.finish() 

117 

118 self.assertEqual(newHorizontalOverscanBBox, 

119 amplifier.getRawHorizontalOverscanBBox()) 

120 self.assertEqual(newVerticalOverscanBBox, 

121 amplifier.getRawVerticalOverscanBBox()) 

122 self.assertEqual(newPrescanBBox, 

123 amplifier.getRawPrescanBBox()) 

124 

125 newPrescanBBox2 = lsst.geom.Box2I( 

126 lsst.geom.Point2I(-20, 2), lsst.geom.Extent2I(5, 306)) 

127 builder.setRawHorizontalPrescanBBox(newPrescanBBox2) 

128 amplifier = builder.finish() 

129 self.assertEqual(newPrescanBBox2, 

130 amplifier.getRawPrescanBBox()) 

131 

132 def test_compareGeometry(self): 

133 """Test the `Amplifier.compareGeometry` method. 

134 

135 This doesn't handle the case where the amplifiers have the same 

136 regions but different assembly state (and hence the regions look 

137 different until they are transformed). That test is in test_transform. 

138 """ 

139 Cmp = AmplifierGeometryComparison 

140 

141 def test_combos(rhs, expected): 

142 """Test all combinations of flag kwargs to compareGeometry. 

143 

144 Parameters 

145 ---------- 

146 rhs : `Amplifier` 

147 RHS of comparison (LHS is always self.amplifier). 

148 expected : `list` of `AmplifierGeometryComparison` 

149 Expected results for comparison (just look at the code for 

150 order). 

151 """ 

152 self.assertIs(self.amplifier.compareGeometry(rhs), expected[0]) 

153 self.assertIs(self.amplifier.compareGeometry(rhs, assembly=False), expected[1]) 

154 self.assertIs(self.amplifier.compareGeometry(rhs, regions=False), expected[2]) 

155 self.assertIs(self.amplifier.compareGeometry(rhs, assembly=False, regions=False), Cmp.EQUAL) 

156 

157 def modified(**kwargs): 

158 """Return an Amplifier by modifying a copy of ``self.amplifier``. 

159 

160 Keyword arguments encode the name of an `Amplifier.Builder` setter 

161 method in the key and the value for that setter in the value. 

162 """ 

163 builder = self.amplifier.rebuild() 

164 for k, v in kwargs.items(): 

165 getattr(builder, k)(v) 

166 return builder.rebuild() 

167 

168 # Comparing an amplifier to itself always returns EQUAL. 

169 test_combos(self.amplifier, [Cmp.EQUAL]*4) 

170 # If we modify a flip or shift parameter we notice iff assembly=true. 

171 # If regions=True as well, then those compare as different because 

172 # the method tries to transform to the same assembly state and that 

173 # makes them different. 

174 test_combos(modified(setRawFlipX=not self.data.rawFlipX), 

175 [Cmp.FLIPPED_X | Cmp.REGIONS_DIFFER, Cmp.EQUAL, Cmp.FLIPPED_X]) 

176 test_combos(modified(setRawFlipY=not self.data.rawFlipY), 

177 [Cmp.FLIPPED_Y | Cmp.REGIONS_DIFFER, Cmp.EQUAL, Cmp.FLIPPED_Y]) 

178 test_combos(modified(setRawXYOffset=self.data.rawXYOffset + lsst.geom.Extent2I(4, 5)), 

179 [Cmp.SHIFTED_X | Cmp.SHIFTED_Y | Cmp.REGIONS_DIFFER, Cmp.EQUAL, 

180 Cmp.SHIFTED_X | Cmp.SHIFTED_Y]) 

181 # If we modify or data box we notice iff regions=True. We modify them 

182 # all by shifting by the same amount because the Amplifier constructor 

183 # would be within its rights to raise if these didn't line up properly, 

184 # and nontrivial modifications that are valid are a lot harder to 

185 # write. 

186 offset = lsst.geom.Extent2I(-2, 3) 

187 test_combos( 

188 modified( 

189 setRawBBox=self.data.rawBBox.shiftedBy(offset), 

190 setRawDataBBox=self.data.rawDataBBox.shiftedBy(offset), 

191 setRawHorizontalOverscanBBox=self.data.rawHorizontalOverscanBBox.shiftedBy(offset), 

192 setRawVerticalOverscanBBox=self.data.rawVerticalOverscanBBox.shiftedBy(offset), 

193 setRawPrescanBBox=self.data.rawPrescanBBox.shiftedBy(offset), 

194 ), 

195 [Cmp.REGIONS_DIFFER, Cmp.REGIONS_DIFFER, Cmp.EQUAL, Cmp.EQUAL], 

196 ) 

197 

198 def test_transform(self): 

199 """Test the `Amplifier.Builder.transform` method and 

200 `Amplifier.compareGeometry` cases that involve post-transform 

201 comparisons. 

202 """ 

203 # Standard case: no kwargs to transform, so we apply flips and 

204 # rawXYOffset shifts. This should leave no flip or offset in the 

205 # returned amplifier. 

206 assembled_amp = self.amplifier.rebuild().transform().finish() 

207 self.assertFalse(assembled_amp.getRawFlipX()) 

208 self.assertFalse(assembled_amp.getRawFlipY()) 

209 self.assertEqual(assembled_amp.getRawXYOffset(), lsst.geom.Extent2I(0, 0)) 

210 self.assertEqual(assembled_amp.getReadoutCorner(), ReadoutCorner.UR) 

211 comparison = self.amplifier.compareGeometry(assembled_amp) 

212 self.assertEqual(bool(comparison & comparison.FLIPPED_X), self.data.rawFlipX) 

213 self.assertEqual(bool(comparison & comparison.FLIPPED_Y), self.data.rawFlipY) 

214 self.assertEqual(bool(comparison & comparison.SHIFTED), 

215 self.data.rawXYOffset != lsst.geom.Extent2I(0, 0)) 

216 self.assertFalse(comparison & comparison.REGIONS_DIFFER) 

217 # Use transform to round-trip the assembled amp back to the original. 

218 roundtripped = assembled_amp.rebuild().transform( 

219 outOffset=self.data.rawXYOffset, 

220 outFlipX=self.data.rawFlipX, 

221 outFlipY=self.data.rawFlipY, 

222 ).finish() 

223 self.assertAmplifiersEqual(self.amplifier, roundtripped) 

224 self.assertIs(self.amplifier.compareGeometry(roundtripped), AmplifierGeometryComparison.EQUAL) 

225 # Transform to completely different offsets and the inverse of the 

226 # flips we tried before, just to make sure we didn't mix up X and Y 

227 # somewhere. 

228 different = self.amplifier.rebuild().transform( 

229 outOffset=lsst.geom.Extent2I(7, 8), 

230 outFlipX=not self.data.rawFlipX, 

231 outFlipY=not self.data.rawFlipY, 

232 ).finish() 

233 self.assertEqual(different.getRawFlipX(), not self.data.rawFlipX) 

234 self.assertEqual(different.getRawFlipY(), not self.data.rawFlipY) 

235 self.assertEqual(different.getRawXYOffset(), lsst.geom.Extent2I(7, 8)) 

236 self.assertEqual(different.getReadoutCorner(), ReadoutCorner.LR) 

237 comparison = self.amplifier.compareGeometry(different) 

238 self.assertTrue(comparison & comparison.ASSEMBLY_DIFFERS) 

239 self.assertFalse(comparison & comparison.REGIONS_DIFFER) 

240 

241 

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

243 pass 

244 

245 

246def setup_module(module): 

247 lsst.utils.tests.init() 

248 

249 

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

251 lsst.utils.tests.init() 

252 unittest.main()