Coverage for tests/test_brighterFatterKernel.py: 19%

87 statements  

« prev     ^ index     » next       coverage.py v7.10.6, created at 2025-10-11 02:31 -0700

1# 

2# LSST Data Management System 

3# 

4# Copyright 2008-2017 AURA/LSST. 

5# 

6# This product includes software developed by the 

7# LSST Project (http://www.lsst.org/). 

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 LSST License Statement and 

20# the GNU General Public License along with this program. If not, 

21# see <https://www.lsstcorp.org/LegalNotices/>. 

22# 

23"""Test cases for lsst.cp.pipe.BrighterFatterKernelSolveTask. 

24""" 

25 

26import unittest 

27import numpy as np 

28 

29import lsst.utils 

30import lsst.utils.tests 

31 

32import lsst.ip.isr as ipIsr 

33import lsst.cp.pipe as cpPipe 

34import lsst.afw.cameraGeom as cameraGeom 

35 

36 

37class BfkSolveTaskTestCase(lsst.utils.tests.TestCase): 

38 """A test case for the brighter fatter kernel solver. 

39 """ 

40 

41 def setUp(self): 

42 """Set up a plausible PTC dataset, with 1% of the expected variance 

43 shifted into covariance terms. 

44 """ 

45 cameraBuilder = cameraGeom.Camera.Builder('fake camera') 

46 detectorWrapper = cameraGeom.testUtils.DetectorWrapper(numAmps=1, cameraBuilder=cameraBuilder) 

47 self.detector = detectorWrapper.detector 

48 self.camera = cameraBuilder.finish() 

49 

50 self.defaultConfig = cpPipe.BrighterFatterKernelSolveConfig() 

51 self.ptc = ipIsr.PhotonTransferCurveDataset(ampNames=['amp 1'], ptcFitType='FULLCOVARIANCE', 

52 covMatrixSide=3) 

53 self.ptc.expIdMask['amp 1'] = np.array([False, True, True, True, True, True, True, True, True, True]) 

54 self.ptc.rawMeans['amp 1'] = np.array([1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000]) 

55 self.ptc.finalMeans['amp 1'] = self.ptc.rawMeans['amp 1'][self.ptc.expIdMask['amp 1']] 

56 self.ptc.rawVars['amp 1'] = 0.99 * np.array(self.ptc.rawMeans['amp 1'], dtype=float) 

57 self.ptc.finalVars['amp 1'] = self.ptc.rawVars['amp 1'][self.ptc.expIdMask['amp 1']] 

58 self.ptc.covariances['amp 1'] = [] 

59 for mean, variance in zip(self.ptc.rawMeans['amp 1'], self.ptc.rawVars['amp 1']): 

60 residual = mean - variance 

61 covariance = [[variance, 0.5 * residual, 0.1 * residual], 

62 [0.2 * residual, 0.1 * residual, 0.05 * residual], 

63 [0.025 * residual, 0.015 * residual, 0.01 * residual]] 

64 self.ptc.covariances['amp 1'].append(covariance) 

65 

66 self.ptc.covariancesModel = self.ptc.covariances 

67 self.ptc.gain['amp 1'] = 1.0 

68 self.ptc.noise['amp 1'] = 5.0 

69 self.ptc.noiseMatrix['amp 1'] = np.zeros((3, 3)) 

70 self.ptc.noiseMatrix['amp 1'][0][0] = self.ptc.noise['amp 1'] 

71 

72 # This is empirically determined from the above parameters. 

73 self.ptc.aMatrix['amp 1'] = np.array([[2.14329806e-06, -4.28659612e-07, -5.35824515e-08], 

74 [-1.07164903e-06, -2.14329806e-07, -3.21494709e-08], 

75 [-2.14329806e-07, -1.07164903e-07, -2.14329806e-08]]) 

76 

77 self.sequencerMetadata = { 

78 "SEQNAME": "a_sequencer", 

79 "SEQFILE": "a_sequencer_file", 

80 "SEQCKSUM": "deadbeef", 

81 } 

82 self.ptc.updateMetadata(**self.sequencerMetadata, setCalibInfo=True) 

83 

84 # This is empirically determined from the above parameters. 

85 self.expectation = np.array([[4.88348887e-08, 1.01136877e-07, 1.51784114e-07, 

86 1.77570668e-07, 1.51784114e-07, 1.01136877e-07, 4.88348887e-08], 

87 [9.42026776e-08, 2.03928507e-07, 3.28428909e-07, 

88 4.06714446e-07, 3.28428909e-07, 2.03928507e-07, 9.42026776e-08], 

89 [1.24047315e-07, 2.70512582e-07, 4.44123665e-07, 

90 5.78099493e-07, 4.44123665e-07, 2.70512582e-07, 1.24047315e-07], 

91 [1.31474000e-07, 2.77801372e-07, 3.85123870e-07, 

92 -5.42128333e-08, 3.85123870e-07, 2.77801372e-07, 1.31474000e-07], 

93 [1.24047315e-07, 2.70512582e-07, 4.44123665e-07, 

94 5.78099493e-07, 4.44123665e-07, 2.70512582e-07, 1.24047315e-07], 

95 [9.42026776e-08, 2.03928507e-07, 3.28428909e-07, 

96 4.06714446e-07, 3.28428909e-07, 2.03928507e-07, 9.42026776e-08], 

97 [4.88348887e-08, 1.01136877e-07, 1.51784114e-07, 

98 1.77570668e-07, 1.51784114e-07, 1.01136877e-07, 4.88348887e-08]]) 

99 

100 def test_averaged(self): 

101 """Test "averaged" brighter-fatter kernel. 

102 """ 

103 task = cpPipe.BrighterFatterKernelSolveTask() 

104 

105 results = task.run(self.ptc, ['this is a dummy exposure'], self.camera, {'detector': 1}) 

106 bfk = results.outputBFK 

107 self.assertFloatsAlmostEqual(bfk.ampKernels['amp 1'], self.expectation, atol=1e-5) 

108 

109 for key, value in self.sequencerMetadata.items(): 

110 self.assertEqual(bfk.metadata[key], value) 

111 

112 self.assertEqual(bfk.metadata["INSTRUME"], self.camera.getName()) 

113 self.assertEqual(bfk.metadata["DETECTOR"], self.detector.getId()) 

114 self.assertEqual(bfk.metadata["DET_NAME"], self.detector.getName()) 

115 self.assertEqual(bfk.metadata["DET_SER"], self.detector.getSerial()) 

116 

117 def test_aMatrix(self): 

118 """Test solution from Astier et al. 2019 "A" matrix 

119 """ 

120 config = cpPipe.BrighterFatterKernelSolveConfig() 

121 config.useAmatrix = True 

122 task = cpPipe.BrighterFatterKernelSolveTask(config=config) 

123 

124 results = task.run(self.ptc, ['this is a dummy exposure'], self.camera, {'detector': 1}) 

125 self.assertFloatsAlmostEqual(results.outputBFK.ampKernels['amp 1'], self.expectation, atol=1e-5) 

126 

127 def test_covSample(self): 

128 """Test solution from Broughton et al. 2024 eq. 4 preKernel 

129 """ 

130 config = cpPipe.BrighterFatterKernelSolveConfig() 

131 config.useCovModelSample = True 

132 config.covModelFluxSample = {'ALL_AMPS': 30000.} 

133 task = cpPipe.BrighterFatterKernelSolveTask(config=config) 

134 

135 results = task.run(self.ptc, ['this is a dummy exposure'], self.camera, {'detector': 1}) 

136 

137 expectation = np.array([[2.19577206e-08, 4.22977941e-08, 5.54871324e-08, 

138 5.85845588e-08, 5.54871324e-08, 4.22977941e-08, 

139 2.19577206e-08], 

140 [4.55330882e-08, 9.17463235e-08, 1.21066176e-07, 

141 1.23363971e-07, 1.21066176e-07, 9.17463235e-08, 

142 4.55330882e-08], 

143 [6.84283088e-08, 1.48088235e-07, 1.98667279e-07, 

144 1.67738971e-07, 1.98667279e-07, 1.48088235e-07, 

145 6.84283088e-08], 

146 [8.00919118e-08, 1.83511029e-07, 2.57775735e-07, 

147 -4.97426471e-08, 2.57775735e-07, 1.83511029e-07, 

148 8.00919118e-08], 

149 [6.84283088e-08, 1.48088235e-07, 1.98667279e-07, 

150 1.67738971e-07, 1.98667279e-07, 1.48088235e-07, 

151 6.84283088e-08], 

152 [4.55330882e-08, 9.17463235e-08, 1.21066176e-07, 

153 1.23363971e-07, 1.21066176e-07, 9.17463235e-08, 

154 4.55330882e-08], 

155 [2.19577206e-08, 4.22977941e-08, 5.54871324e-08, 

156 5.85845588e-08, 5.54871324e-08, 4.22977941e-08, 

157 2.19577206e-08]]) 

158 

159 self.assertFloatsAlmostEqual(results.outputBFK.ampKernels['amp 1'], expectation, atol=1e-5) 

160 

161 def test_quadratic(self): 

162 """Test quadratic correlation solver. 

163 

164 This requires a different model for the variance, so cannot 

165 use the one generated by setUp. This model is not entirely 

166 physical, but will ensure that accidental code changes are 

167 detected. 

168 """ 

169 config = cpPipe.BrighterFatterKernelSolveConfig() 

170 config.correlationQuadraticFit = True 

171 config.forceZeroSum = True 

172 task = cpPipe.BrighterFatterKernelSolveTask(config=config) 

173 

174 ptc = ipIsr.PhotonTransferCurveDataset(ampNames=['amp 1'], ptcFitType='FULLCOVARIANCE', 

175 covMatrixSide=3) 

176 ptc.expIdMask['amp 1'] = np.array([False, True, True, True, True, True, True, True, True, True]) 

177 ptc.rawMeans['amp 1'] = np.array([1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000]) 

178 ptc.finalMeans['amp 1'] = ptc.rawMeans['amp 1'][ptc.expIdMask['amp 1']] 

179 ptc.rawVars['amp 1'] = 9e-5 * np.square(np.array(ptc.rawMeans['amp 1'], dtype=float)) 

180 ptc.finalVars['amp 1'] = ptc.rawVars['amp 1'][ptc.expIdMask['amp 1']] 

181 ptc.covariances['amp 1'] = [] 

182 for mean, variance in zip(ptc.rawMeans['amp 1'], ptc.rawVars['amp 1']): 

183 residual = variance 

184 covariance = [[variance, 0.5 * residual, 0.1 * residual], 

185 [0.2 * residual, 0.1 * residual, 0.05 * residual], 

186 [0.025 * residual, 0.015 * residual, 0.01 * residual]] 

187 ptc.covariances['amp 1'].append(covariance) 

188 

189 ptc.gain['amp 1'] = 1.0 

190 ptc.noise['amp 1'] = 5.0 

191 

192 results = task.run(ptc, ['this is a dummy exposure'], self.camera, {'detector': 1}) 

193 

194 expectation = np.array([[4.05330882e-08, 2.26654412e-07, 5.66636029e-07, 7.56066176e-07, 

195 5.66636029e-07, 2.26654412e-07, 4.05330882e-08], 

196 [-6.45220588e-08, 2.99448529e-07, 1.28382353e-06, 1.89099265e-06, 

197 1.28382353e-06, 2.99448529e-07, -6.45220588e-08], 

198 [-5.98069853e-07, -1.14816176e-06, -2.12178309e-06, -4.75974265e-06, 

199 -2.12178309e-06, -1.14816176e-06, -5.98069853e-07], 

200 [-1.17959559e-06, -3.52224265e-06, -1.28630515e-05, -6.16863971e-05, 

201 -1.28630515e-05, -3.52224265e-06, -1.17959559e-06], 

202 [-5.98069853e-07, -1.14816176e-06, -2.12178309e-06, -4.75974265e-06, 

203 -2.12178309e-06, -1.14816176e-06, -5.98069853e-07], 

204 [-6.45220588e-08, 2.99448529e-07, 1.28382353e-06, 1.89099265e-06, 

205 1.28382353e-06, 2.99448529e-07, -6.45220588e-08], 

206 [4.05330882e-08, 2.26654412e-07, 5.66636029e-07, 7.56066176e-07, 

207 5.66636029e-07, 2.26654412e-07, 4.05330882e-08]]) 

208 self.assertFloatsAlmostEqual(results.outputBFK.ampKernels['amp 1'], expectation, atol=1e-5) 

209 

210 

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

212 pass 

213 

214 

215def setup_module(module): 

216 lsst.utils.tests.init() 

217 

218 

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

220 lsst.utils.tests.init() 

221 unittest.main()