Coverage for python/lsst/cp/verify/verifyPtc.py: 16%
86 statements
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-22 11:17 +0000
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-22 11:17 +0000
1# This file is part of cp_verify.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (http://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 <http://www.gnu.org/licenses/>.
21import numpy as np
22import lsst.pex.config as pexConfig
24from .verifyCalib import CpVerifyCalibConfig, CpVerifyCalibTask, CpVerifyCalibConnections
26__all__ = ['CpVerifyPtcConfig', 'CpVerifyPtcTask']
29class CpVerifyPtcConfig(CpVerifyCalibConfig,
30 pipelineConnections=CpVerifyCalibConnections):
31 """Inherits from base CpVerifyCalibConfig."""
33 def setDefaults(self):
34 super().setDefaults()
36 gainThreshold = pexConfig.Field(
37 dtype=float,
38 doc="Maximum percentage difference between PTC gain and nominal amplifier gain.",
39 default=5.0,
40 )
42 noiseThreshold = pexConfig.Field(
43 dtype=float,
44 doc="Maximum percentage difference between PTC readout noise and nominal "
45 "amplifier readout noise.",
46 default=5.0,
47 )
49 turnoffThreshold = pexConfig.Field(
50 dtype=float,
51 doc="Minimun full well requirement (in electrons). To be compared with the "
52 "reported PTC turnoff per amplifier.",
53 default=90000,
54 )
56 a00MinITL = pexConfig.Field(
57 dtype=float,
58 doc="Minimum a00 (c.f., Astier+19) for ITL CCDs.",
59 default=-4.56e-6,
60 )
62 a00MaxITL = pexConfig.Field(
63 dtype=float,
64 doc="Maximum a00 (c.f., Astier+19) for ITL CCDs.",
65 default=6.91e-7,
66 )
68 a00MinE2V = pexConfig.Field(
69 dtype=float,
70 doc="Minimum a00 (c.f., Astier+19) for E2V CCDs.",
71 default=-3.52e-6,
72 )
74 a00MaxE2V = pexConfig.Field(
75 dtype=float,
76 doc="Maximum a00 (c.f., Astier+19) for E2V CCDs.",
77 default=-2.61e-6,
78 )
81class CpVerifyPtcTask(CpVerifyCalibTask):
82 """PTC verification sub-class, implementing the verify method.
83 """
84 ConfigClass = CpVerifyPtcConfig
85 _DefaultName = 'cpVerifyPtc'
87 def detectorStatistics(self, inputCalib, camera=None):
88 """Calculate detector level statistics from the calibration.
90 Parameters
91 ----------
92 inputCalib : `lsst.ip.isr.IsrCalib`
93 The calibration to verify.
94 camera : `lsst.afw.cameraGeom.Camera`, optional
95 Input camera to get detectors from.
97 Returns
98 -------
99 outputStatistics : `dict` [`str`, scalar]
100 A dictionary of the statistics measured and their values.
101 """
102 return {}
104 def amplifierStatistics(self, inputCalib, camera=None):
105 """Calculate detector level statistics from the calibration.
107 Parameters
108 ----------
109 inputCalib : `lsst.ip.isr.IsrCalib`
110 The calibration to verify.
111 camera : `lsst.afw.cameraGeom.Camera`, optional
112 Input camera to get detectors from.
114 Returns
115 -------
116 outputStatistics : `dict` [`str`, scalar]
117 A dictionary of the statistics measured and their values.
118 """
119 calibMetadata = inputCalib.getMetadata().toDict()
120 detId = calibMetadata['DETECTOR']
121 detector = camera[detId]
122 ptcFitType = calibMetadata['PTC_FIT_TYPE']
123 outputStatistics = {amp.getName(): {} for amp in detector}
124 for amp in detector:
125 ampName = amp.getName()
126 outputStatistics[ampName]['PTC_GAIN'] = inputCalib.gain[ampName]
127 outputStatistics[ampName]['AMP_GAIN'] = amp.getGain()
128 outputStatistics[ampName]['PTC_NOISE'] = inputCalib.noise[ampName]
129 outputStatistics[ampName]['AMP_NOISE'] = amp.getReadNoise()
130 outputStatistics[ampName]['PTC_TURNOFF'] = inputCalib.ptcTurnoff[ampName]
131 outputStatistics[ampName]['PTC_FIT_TYPE'] = ptcFitType
132 if ptcFitType == 'EXPAPPROXIMATION':
133 outputStatistics[ampName]['PTC_BFE_A00'] = float(inputCalib.ptcFitPars[ampName][0])
134 if ptcFitType == 'FULLCOVARIANCE':
135 outputStatistics[ampName]['PTC_BFE_A00'] = float(inputCalib.aMatrix[ampName][0][0])
136 return outputStatistics
138 def verify(self, calib, statisticsDict, camera=None):
139 """Verify that the calibration meets the verification criteria.
141 Parameters
142 ----------
143 inputCalib : `lsst.ip.isr.IsrCalib`
144 The calibration to verify.
145 statisticsDictionary : `dict` [`str`, `dict` [`str`, scalar]],
146 Dictionary of measured statistics. The inner dictionary
147 should have keys that are statistic names (`str`) with
148 values that are some sort of scalar (`int` or `float` are
149 the mostly likely types).
150 camera : `lsst.afw.cameraGeom.Camera`, optional
151 Input camera to get detectors from.
153 Returns
154 -------
155 outputStatistics : `dict` [`str`, `dict` [`str`, `bool`]]
156 A dictionary indexed by the amplifier name, containing
157 dictionaries of the verification criteria.
158 success : `bool`
159 A boolean indicating whether all tests have passed.
160 """
161 verifyStats = {}
162 success = True
163 calibMetadata = calib.getMetadata().toDict()
164 detId = calibMetadata['DETECTOR']
165 detector = camera[detId]
166 ptcFitType = calibMetadata['PTC_FIT_TYPE']
167 # 'DET_SER' is of the form 'ITL-3800C-229'
168 detVendor = calibMetadata['DET_SER'].split('-')[0]
170 for amp in detector:
171 verify = {}
172 ampName = amp.getName()
173 diffGain = (np.abs(calib.gain[ampName] - amp.getGain()) / amp.getGain())*100
174 diffNoise = (np.abs(calib.noise[ampName] - amp.getReadNoise()) / amp.getReadNoise())*100
176 # DMTN-101: 16.1 and 16.2
177 # The fractional relative difference between the fitted PTC and the
178 # nominal amplifier gain and readout noise values should be less
179 # than a certain threshold (default: 5%).
180 verify['GAIN'] = bool(diffGain < self.config.gainThreshold)
181 verify['NOISE'] = bool(diffNoise < self.config.noiseThreshold)
183 # DMTN-101: 16.3
184 # Check that the measured PTC turnoff is at least greater than the
185 # full-well requirement of 90k e-.
186 turnoffCut = self.config.turnoffThreshold
187 verify['PTC_TURNOFF'] = bool(calib.ptcTurnoff[ampName]*calib.gain[ampName] > turnoffCut)
188 # DMTN-101: 16.4
189 # Check the a00 value (brighter-fatter effect).
190 # This is a purely electrostatic parameter that should not change
191 # unless voltages are changed (e.g., parallel, bias voltages).
192 # Check that the fitted a00 parameter per CCD vendor is within a
193 # range motivated by measurements on data (DM-30171).
194 if ptcFitType in ['EXPAPPROXIMATION', 'FULLCOVARIANCE']:
195 # a00 is a fit parameter from these models.
196 if ptcFitType == 'EXPAPPROXIMATION':
197 a00 = calib.ptcFitPars[ampName][0]
198 else:
199 a00 = calib.aMatrix[ampName][0][0]
200 if detVendor == 'ITL':
201 a00Max = self.config.a00MaxITL
202 a00Min = self.config.a00MinITL
203 verify['BFE_A00'] = bool(a00 > a00Min and a00 < a00Max)
204 elif detVendor == 'E2V':
205 a00Max = self.config.a00MaxE2V
206 a00Min = self.config.a00MinE2V
207 verify['BFE_A00'] = bool(a00 > a00Min and a00 < a00Max)
208 else:
209 raise RuntimeError(f"Detector type {detVendor} not one of 'ITL' or 'E2V'")
210 # Overall success among all tests for this amp.
211 verify['SUCCESS'] = bool(np.all(list(verify.values())))
212 if verify['SUCCESS'] is False:
213 success = False
215 verifyStats[ampName] = verify
217 # Loop over amps to make a detector summary.
218 verifyDetStats = {'GAIN': [], 'NOISE': [], 'PTC_TURNOFF': [], 'BFE_A00': []}
219 for amp in verifyStats:
220 for testName in verifyStats[amp]:
221 if testName == 'SUCCESS':
222 continue
223 verifyDetStats[testName].append(verifyStats[amp][testName])
225 # If ptc model did not fit for a00 (e.g., POLYNOMIAL)
226 if not len(verifyDetStats['BFE_A00']):
227 verifyDetStats.pop('BFE_A00')
229 # VerifyDetStatsFinal has final boolean test over all amps
230 verifyDetStatsFinal = {}
231 for testName in verifyDetStats:
232 testBool = bool(np.all(list(verifyDetStats[testName])))
233 # Save the tests that failed
234 if not testBool:
235 verifyDetStatsFinal[testName] = bool(np.all(list(verifyDetStats[testName])))
237 return verifyDetStatsFinal, bool(success)