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