Coverage for python/lsst/cp/verify/verifyPtc.py: 18%

89 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2022-10-18 02:29 -0700

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 

24import lsst.pipe.base.connectionTypes as cT 

25from .verifyCalib import CpVerifyCalibConfig, CpVerifyCalibTask, CpVerifyCalibConnections 

26 

27__all__ = ['CpVerifyPtcConnections', 'CpVerifyPtcConfig', 'CpVerifyPtcTask'] 

28 

29 

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 ) 

40 

41 

42class CpVerifyPtcConfig(CpVerifyCalibConfig, 

43 pipelineConnections=CpVerifyPtcConnections): 

44 """Inherits from base CpVerifyCalibConfig.""" 

45 

46 def setDefaults(self): 

47 super().setDefaults() 

48 

49 gainThreshold = pexConfig.Field( 

50 dtype=float, 

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

52 default=5.0, 

53 ) 

54 

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 ) 

61 

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 ) 

68 

69 a00MinITL = pexConfig.Field( 

70 dtype=float, 

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

72 default=-4.56e-6, 

73 ) 

74 

75 a00MaxITL = pexConfig.Field( 

76 dtype=float, 

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

78 default=6.91e-7, 

79 ) 

80 

81 a00MinE2V = pexConfig.Field( 

82 dtype=float, 

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

84 default=-3.52e-6, 

85 ) 

86 

87 a00MaxE2V = pexConfig.Field( 

88 dtype=float, 

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

90 default=-2.61e-6, 

91 ) 

92 

93 

94class CpVerifyPtcTask(CpVerifyCalibTask): 

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

96 """ 

97 ConfigClass = CpVerifyPtcConfig 

98 _DefaultName = 'cpVerifyPtc' 

99 

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

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

102 

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. 

109 

110 Returns 

111 ------- 

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

113 A dictionary of the statistics measured and their values. 

114 """ 

115 return {} 

116 

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

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

119 

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. 

126 

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] 

149 

150 return outputStatistics 

151 

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

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

154 

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. 

166 

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] 

183 

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 

189 

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) 

196 

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 

228 

229 verifyStats[ampName] = verify 

230 

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

238 

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

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

241 verifyDetStats.pop('BFE_A00') 

242 

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

250 

251 return verifyDetStatsFinal, bool(success)