Coverage for tests / test_brighterFatterKernel.py: 19%

89 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-15 00:22 +0000

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 = np.array([[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 # The covariances must be a numpy array 

67 self.ptc.covariances['amp 1'] = np.array(self.ptc.covariances['amp 1']) 

68 

69 self.ptc.covariancesModel = self.ptc.covariances 

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

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

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

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

74 

75 # This is empirically determined from the above parameters. 

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

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

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

79 

80 self.sequencerMetadata = { 

81 "SEQNAME": "a_sequencer", 

82 "SEQFILE": "a_sequencer_file", 

83 "SEQCKSUM": "deadbeef", 

84 } 

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

86 

87 # This is empirically determined from the above parameters. 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

102 

103 def test_averaged(self): 

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

105 """ 

106 task = cpPipe.BrighterFatterKernelSolveTask() 

107 

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

109 bfk = results.outputBFK 

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

111 

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

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

114 

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

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

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

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

119 

120 def test_aMatrix(self): 

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

122 """ 

123 config = cpPipe.BrighterFatterKernelSolveConfig() 

124 config.useAmatrix = True 

125 task = cpPipe.BrighterFatterKernelSolveTask(config=config) 

126 

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

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

129 

130 def test_covSample(self): 

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

132 """ 

133 config = cpPipe.BrighterFatterKernelSolveConfig() 

134 config.useCovModelSample = True 

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

136 task = cpPipe.BrighterFatterKernelSolveTask(config=config) 

137 

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

139 

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

141 5.85845588e-08, 5.54871324e-08, 4.22977941e-08, 

142 2.19577206e-08], 

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

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

145 4.55330882e-08], 

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

147 1.67738971e-07, 1.98667279e-07, 1.48088235e-07, 

148 6.84283088e-08], 

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

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

151 8.00919118e-08], 

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

153 1.67738971e-07, 1.98667279e-07, 1.48088235e-07, 

154 6.84283088e-08], 

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

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

157 4.55330882e-08], 

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

159 5.85845588e-08, 5.54871324e-08, 4.22977941e-08, 

160 2.19577206e-08]]) 

161 

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

163 

164 def test_quadratic(self): 

165 """Test quadratic correlation solver. 

166 

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

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

169 physical, but will ensure that accidental code changes are 

170 detected. 

171 """ 

172 config = cpPipe.BrighterFatterKernelSolveConfig() 

173 config.correlationQuadraticFit = True 

174 config.forceZeroSum = True 

175 task = cpPipe.BrighterFatterKernelSolveTask(config=config) 

176 

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

178 covMatrixSide=3) 

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

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

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

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

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

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

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

186 residual = variance 

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

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

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

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

191 

192 # The covariances must be a numpy array 

193 ptc.covariances['amp 1'] = np.array(ptc.covariances['amp 1']) 

194 

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

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

197 

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

199 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

215 

216 

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

218 pass 

219 

220 

221def setup_module(module): 

222 lsst.utils.tests.init() 

223 

224 

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

226 lsst.utils.tests.init() 

227 unittest.main()