Coverage for tests / test_brighterFatterKernel.py: 19%
89 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-05 08:44 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-05 08:44 +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"""
26import unittest
27import numpy as np
29import lsst.utils
30import lsst.utils.tests
32import lsst.ip.isr as ipIsr
33import lsst.cp.pipe as cpPipe
34import lsst.afw.cameraGeom as cameraGeom
37class BfkSolveTaskTestCase(lsst.utils.tests.TestCase):
38 """A test case for the brighter fatter kernel solver.
39 """
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()
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)
66 # The covariances must be a numpy array
67 self.ptc.covariances['amp 1'] = np.array(self.ptc.covariances['amp 1'])
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']
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]])
80 self.sequencerMetadata = {
81 "SEQNAME": "a_sequencer",
82 "SEQFILE": "a_sequencer_file",
83 "SEQCKSUM": "deadbeef",
84 }
85 self.ptc.updateMetadata(**self.sequencerMetadata, setCalibInfo=True)
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]])
103 def test_averaged(self):
104 """Test "averaged" brighter-fatter kernel.
105 """
106 task = cpPipe.BrighterFatterKernelSolveTask()
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)
112 for key, value in self.sequencerMetadata.items():
113 self.assertEqual(bfk.metadata[key], value)
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())
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)
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)
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)
138 results = task.run(self.ptc, ['this is a dummy exposure'], self.camera, {'detector': 1})
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]])
162 self.assertFloatsAlmostEqual(results.outputBFK.ampKernels['amp 1'], expectation, atol=1e-5)
164 def test_quadratic(self):
165 """Test quadratic correlation solver.
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)
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)
192 # The covariances must be a numpy array
193 ptc.covariances['amp 1'] = np.array(ptc.covariances['amp 1'])
195 ptc.gain['amp 1'] = 1.0
196 ptc.noise['amp 1'] = 5.0
198 results = task.run(ptc, ['this is a dummy exposure'], self.camera, {'detector': 1})
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)
217class TestMemory(lsst.utils.tests.MemoryTestCase):
218 pass
221def setup_module(module):
222 lsst.utils.tests.init()
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()