Coverage for tests/test_brighterFatterKernel.py: 19%
87 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-07 02:49 -0800
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-07 02:49 -0800
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 = [[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 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']
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]])
77 self.sequencerMetadata = {
78 "SEQNAME": "a_sequencer",
79 "SEQFILE": "a_sequencer_file",
80 "SEQCKSUM": "deadbeef",
81 }
82 self.ptc.updateMetadata(**self.sequencerMetadata, setCalibInfo=True)
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]])
100 def test_averaged(self):
101 """Test "averaged" brighter-fatter kernel.
102 """
103 task = cpPipe.BrighterFatterKernelSolveTask()
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)
109 for key, value in self.sequencerMetadata.items():
110 self.assertEqual(bfk.metadata[key], value)
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())
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)
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)
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)
135 results = task.run(self.ptc, ['this is a dummy exposure'], self.camera, {'detector': 1})
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]])
159 self.assertFloatsAlmostEqual(results.outputBFK.ampKernels['amp 1'], expectation, atol=1e-5)
161 def test_quadratic(self):
162 """Test quadratic correlation solver.
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)
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)
189 ptc.gain['amp 1'] = 1.0
190 ptc.noise['amp 1'] = 5.0
192 results = task.run(ptc, ['this is a dummy exposure'], self.camera, {'detector': 1})
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)
211class TestMemory(lsst.utils.tests.MemoryTestCase):
212 pass
215def setup_module(module):
216 lsst.utils.tests.init()
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()