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

75 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-22 09:10 +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 

24import lsst.pipe.base.connectionTypes as cT 

25from .verifyCalib import CpVerifyCalibConfig, CpVerifyCalibTask, CpVerifyCalibConnections 

26 

27__all__ = ['CpVerifyGainConnections', 'CpVerifyGainConfig', 'CpVerifyGainTask'] 

28 

29 

30class CpVerifyGainConnections(CpVerifyCalibConnections, 

31 dimensions={"instrument", "detector"}, 

32 defaultTemplates={}): 

33 exposure = cT.Input( 

34 name="raw", 

35 doc="Exposure ID of first flat from flat pair.", 

36 storageClass='Exposure', 

37 dimensions=("instrument", "detector", "exposure"), 

38 multiple=True, 

39 deferLoad=True, 

40 ) 

41 

42 inputCalib = cT.Input( 

43 name="calib", 

44 doc="Input calib to calculate statistics for.", 

45 storageClass="PhotonTransferCurveDataset", 

46 dimensions=("instrument", "detector", "exposure"), 

47 isCalibration=True 

48 ) 

49 

50 

51class CpVerifyGainConfig(CpVerifyCalibConfig, 

52 pipelineConnections=CpVerifyGainConnections): 

53 """Inherits from base CpVerifyCalibConfig.""" 

54 

55 gainThreshold = pexConfig.Field( 

56 dtype=float, 

57 doc="Maximum percentage difference between gain from flat pairs and nominal amplifier gain.", 

58 default=5.0, 

59 ) 

60 

61 noiseThreshold = pexConfig.Field( 

62 dtype=float, 

63 doc="Maximum percentage difference between overscan readout noise and nominal " 

64 "amplifier readout noise.", 

65 default=5.0, 

66 ) 

67 

68 def setDefaults(self): 

69 super().setDefaults() 

70 self.stageName = 'GAIN' 

71 

72 

73class CpVerifyGainTask(CpVerifyCalibTask): 

74 """Gain from flat pairs verification sub-class. Implements verify method. 

75 """ 

76 ConfigClass = CpVerifyGainConfig 

77 _DefaultName = 'cpVerifyGain' 

78 

79 def detectorStatistics(self, inputCalib, camera=None, exposure=None): 

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

81 

82 Parameters 

83 ---------- 

84 inputCalib : `lsst.ip.isr.IsrCalib` 

85 The calibration to verify. 

86 camera : `lsst.afw.cameraGeom.Camera`, optional 

87 Input camera to get detectors from. 

88 exposure : `lsst.afw.image.exposure.ExposureF`, optional 

89 First flat-field image from pair of flats used to 

90 estimate the gain. 

91 

92 Returns 

93 ------- 

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

95 A dictionary of the statistics measured and their values. 

96 """ 

97 return {} 

98 

99 def amplifierStatistics(self, inputCalib, camera=None, exposure=None): 

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

101 

102 Parameters 

103 ---------- 

104 inputCalib : `lsst.ip.isr.IsrCalib` 

105 The calibration to verify. 

106 camera : `lsst.afw.cameraGeom.Camera`, optional 

107 Input camera to get detectors from. 

108 exposure : `lsst.afw.image.exposure.ExposureF`, optional 

109 First flat-field image from pair of flats used to 

110 estimate the gain. 

111 

112 Returns 

113 ------- 

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

115 A dictionary of the statistics measured and their values. 

116 """ 

117 calibMetadata = inputCalib.getMetadata().toDict() 

118 detId = calibMetadata['DETECTOR'] 

119 detector = camera[detId] 

120 # 'DET_SER' is of the form 'ITL-3800C-229' 

121 detVendor = calibMetadata['DET_SER'].split('-')[0] 

122 # Adjust gain estimated from flat pair for flux bias, see DM-35790 

123 if detVendor == 'ITL': 

124 slope = 0.00027 # %/ADU 

125 elif detVendor == 'e2V': 

126 slope = 0.00046 # %/ADU 

127 outputStatistics = {amp.getName(): {} for amp in detector} 

128 

129 for amp in detector: 

130 ampName = amp.getName() 

131 # Flux correction to gain-per-flat-pair method, see DM-35790 

132 correction = 1. - slope*inputCalib.rawMeans[ampName][0]/100 

133 outputStatistics[ampName]['MEAN_FLUX_FLAT_PAIR'] = inputCalib.rawMeans[ampName][0] 

134 outputStatistics[ampName]['GAIN_FROM_FLAT_PAIR'] = inputCalib.gain[ampName] 

135 outputStatistics[ampName]['GAIN_CORRECTION_FACTOR'] = correction 

136 outputStatistics[ampName]['AMP_GAIN'] = amp.getGain() 

137 outputStatistics[ampName]['ISR_NOISE'] = inputCalib.noise[ampName] 

138 outputStatistics[ampName]['AMP_NOISE'] = amp.getReadNoise() 

139 

140 return outputStatistics 

141 

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

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

144 

145 Parameters 

146 ---------- 

147 inputCalib : `lsst.ip.isr.IsrCalib` 

148 The calibration to verify. 

149 statisticsDictionary : `dict` [`str`, `dict` [`str`, scalar]], 

150 Dictionary of measured statistics. The inner dictionary 

151 should have keys that are statistic names (`str`) with 

152 values that are some sort of scalar (`int` or `float` are 

153 the mostly likely types). 

154 camera : `lsst.afw.cameraGeom.Camera`, optional 

155 Input camera to get detectors from. 

156 exposure : `lsst.afw.image.exposure.ExposureF`, optional 

157 First flat-field image from pair of flats used to 

158 estimate the gain. 

159 

160 Returns 

161 ------- 

162 outputStatistics : `dict` [`str`, `dict` [`str`, `bool`]] 

163 A dictionary indexed by the amplifier name, containing 

164 dictionaries of the verification criteria. 

165 success : `bool` 

166 A boolean indicating whether all tests have passed. 

167 """ 

168 verifyStats = {} 

169 success = True 

170 calibMetadata = calib.getMetadata().toDict() 

171 detId = calibMetadata['DETECTOR'] 

172 detector = camera[detId] 

173 # 'DET_SER' is of the form 'ITL-3800C-229' 

174 detVendor = calibMetadata['DET_SER'].split('-')[0] 

175 # Adjust gain estimated from flat pair for flux bias, see DM-35790 

176 if detVendor == 'ITL': 

177 slope = 0.00027 # %/ADU 

178 elif detVendor == 'e2V': 

179 slope = 0.00046 # %/ADU 

180 

181 for amp in detector: 

182 verify = {} 

183 ampName = amp.getName() 

184 

185 # Gain from a pair of flats and noise from overscan after ISR. 

186 # See DM-35790. 

187 correction = 1. - slope*np.array(calib.rawMeans[ampName][0])/100 

188 gain = correction*calib.gain[ampName] 

189 

190 diffGain = (np.abs(gain - amp.getGain()) / amp.getGain())*100 

191 diffNoise = (np.abs(calib.noise[ampName] - amp.getReadNoise()) / amp.getReadNoise())*100 

192 

193 # DMTN-101: 6.1 and 6.2 

194 # Estimate gain from a pair of flats and compare it with the value 

195 # in the amplifiers. 

196 verify['GAIN_FROM_FLAT_PAIR'] = bool(diffGain < self.config.gainThreshold) 

197 # Check the empirical noise (as oppossed to fitted noise 

198 # from the PTC) calculated from the overscan after ISR. 

199 verify['ISR_NOISE'] = bool(diffNoise < self.config.noiseThreshold) 

200 

201 # Overall success among all tests for this amp. 

202 verify['SUCCESS'] = bool(np.all(list(verify.values()))) 

203 if verify['SUCCESS'] is False: 

204 success = False 

205 

206 verifyStats[ampName] = verify 

207 

208 # Loop over amps to make a detector summary. 

209 verifyDetStats = {'GAIN_FROM_FLAT_PAIR': [], 'ISR_NOISE': []} 

210 for amp in verifyStats: 

211 for testName in verifyStats[amp]: 

212 if testName == 'SUCCESS': 

213 continue 

214 verifyDetStats[testName].append(verifyStats[amp][testName]) 

215 

216 # VerifyDetStatsFinal has final boolean test over all amps 

217 verifyDetStatsFinal = {} 

218 for testName in verifyDetStats: 

219 testBool = bool(np.all(list(verifyDetStats[testName]))) 

220 # Save the tests that failed 

221 if not testBool: 

222 verifyDetStatsFinal[testName] = bool(np.all(list(verifyDetStats[testName]))) 

223 

224 return verifyDetStatsFinal, bool(success)