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

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 

23 

24from .verifyCalib import CpVerifyCalibConfig, CpVerifyCalibTask, CpVerifyCalibConnections 

25 

26__all__ = ['CpVerifyPtcConfig', 'CpVerifyPtcTask'] 

27 

28 

29class CpVerifyPtcConfig(CpVerifyCalibConfig, 

30 pipelineConnections=CpVerifyCalibConnections): 

31 """Inherits from base CpVerifyCalibConfig.""" 

32 

33 def setDefaults(self): 

34 super().setDefaults() 

35 

36 gainThreshold = pexConfig.Field( 

37 dtype=float, 

38 doc="Maximum percentage difference between PTC gain and nominal amplifier gain.", 

39 default=5.0, 

40 ) 

41 

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 ) 

48 

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 ) 

55 

56 a00MinITL = pexConfig.Field( 

57 dtype=float, 

58 doc="Minimum a00 (c.f., Astier+19) for ITL CCDs.", 

59 default=-4.56e-6, 

60 ) 

61 

62 a00MaxITL = pexConfig.Field( 

63 dtype=float, 

64 doc="Maximum a00 (c.f., Astier+19) for ITL CCDs.", 

65 default=6.91e-7, 

66 ) 

67 

68 a00MinE2V = pexConfig.Field( 

69 dtype=float, 

70 doc="Minimum a00 (c.f., Astier+19) for E2V CCDs.", 

71 default=-3.52e-6, 

72 ) 

73 

74 a00MaxE2V = pexConfig.Field( 

75 dtype=float, 

76 doc="Maximum a00 (c.f., Astier+19) for E2V CCDs.", 

77 default=-2.61e-6, 

78 ) 

79 

80 

81class CpVerifyPtcTask(CpVerifyCalibTask): 

82 """PTC verification sub-class, implementing the verify method. 

83 """ 

84 ConfigClass = CpVerifyPtcConfig 

85 _DefaultName = 'cpVerifyPtc' 

86 

87 def detectorStatistics(self, inputCalib, camera=None): 

88 """Calculate detector level statistics from the calibration. 

89 

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. 

96 

97 Returns 

98 ------- 

99 outputStatistics : `dict` [`str`, scalar] 

100 A dictionary of the statistics measured and their values. 

101 """ 

102 return {} 

103 

104 def amplifierStatistics(self, inputCalib, camera=None): 

105 """Calculate detector level statistics from the calibration. 

106 

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. 

113 

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 

137 

138 def verify(self, calib, statisticsDict, camera=None): 

139 """Verify that the calibration meets the verification criteria. 

140 

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. 

152 

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] 

169 

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 

175 

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) 

182 

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 

214 

215 verifyStats[ampName] = verify 

216 

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]) 

224 

225 # If ptc model did not fit for a00 (e.g., POLYNOMIAL) 

226 if not len(verifyDetStats['BFE_A00']): 

227 verifyDetStats.pop('BFE_A00') 

228 

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]))) 

236 

237 return verifyDetStatsFinal, bool(success)