Coverage for python/lsst/cp/verify/verifyFlat.py: 15%

102 statements  

« prev     ^ index     » next       coverage.py v7.5.1, created at 2024-05-09 12:33 +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.afw.math as afwMath 

23from .verifyStats import CpVerifyStatsConfig, CpVerifyStatsTask, CpVerifyStatsConnections 

24from .mergeResults import CpVerifyExpMergeByFilterConfig, CpVerifyExpMergeByFilterTask 

25 

26__all__ = ['CpVerifyFlatConfig', 'CpVerifyFlatTask', 

27 'CpVerifyFlatExpMergeConfig', 'CpVerifyFlatExpMergeTask'] 

28# 'CpVerifyFlatRunMergeConfig', 'CpVerifyFlatRunMergeTask',] 

29 

30 

31class CpVerifyFlatConfig(CpVerifyStatsConfig, 

32 pipelineConnections=CpVerifyStatsConnections): 

33 """Inherits from base CpVerifyStatsConfig. 

34 """ 

35 

36 def setDefaults(self): 

37 super().setDefaults() 

38 self.stageName = 'FLAT' 

39 self.imageStatKeywords = {'MEAN': 'MEAN', # noqa F841 

40 'NOISE': 'STDEVCLIP', } 

41 self.detectorStatKeywords = {'MEAN': 'MEAN', # noqa F841 

42 'SCATTER': 'STDEV', } 

43 

44 

45class CpVerifyFlatTask(CpVerifyStatsTask): 

46 """Flat verification sub-class, implementing the verify method. 

47 """ 

48 ConfigClass = CpVerifyFlatConfig 

49 _DefaultName = 'cpVerifyFlat' 

50 

51 def detectorStatistics(self, statisticsDict, statControl, exposure=None, uncorrectedExposure=None): 

52 """Calculate detector level statistics based on the existing 

53 per-amplifier measurements. 

54 

55 Parameters 

56 ---------- 

57 statisticsDict : `dict` [`str`, `dict` [`str`, scalar]], 

58 Dictionary of measured statistics. The inner dictionary 

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

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

61 the mostly likely types). 

62 statControl : `lsst.afw.math.StatControl` 

63 Statistics control object with parameters defined by 

64 the config. 

65 exposure : `lsst.afw.image.Exposure`, optional 

66 Exposure containing the ISR-processed data to measure. 

67 uncorrectedExposure : `lsst.afw.image.Exposure`, optional 

68 uncorrected esposure (no defects) containing the 

69 ISR-processed data to measure. 

70 

71 Returns 

72 ------- 

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

74 A dictionary of the statistics measured and their values. 

75 

76 """ 

77 outputStatistics = {} 

78 

79 ampStats = statisticsDict['AMP'] 

80 amplifierMeans = [stats['MEAN'] for stats in ampStats.values()] 

81 

82 statisticToRun, statAccessor = self._configHelper(self.config.detectorStatKeywords) 

83 stats = afwMath.makeStatistics(amplifierMeans, statisticToRun, statControl) 

84 

85 for k, v in statAccessor.items(): 

86 outputStatistics[k] = stats.getValue(v) 

87 

88 return outputStatistics 

89 

90 def verify(self, exposure, statisticsDict): 

91 """Verify that the measured statistics meet the verification criteria. 

92 

93 Parameters 

94 ---------- 

95 exposure : `lsst.afw.image.Exposure` 

96 The exposure the statistics are from. 

97 statisticsDict : `dict` [`str`, `dict` [`str`, scalar]], 

98 Dictionary of measured statistics. The inner dictionary 

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

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

101 the mostly likely types). 

102 

103 Returns 

104 ------- 

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

106 A dictionary indexed by the amplifier name, containing 

107 dictionaries of the verification criteria. 

108 success : `bool` 

109 A boolean indicating if all tests have passed. 

110 """ 

111 ampStats = statisticsDict['AMP'] 

112 verifyStats = {} 

113 success = True 

114 for ampName, stats in ampStats.items(): 

115 verify = {} 

116 

117 # DMTN-101 Test 10.X: confirm that per-amplifier scatter is 

118 # consistent with Poissonian 

119 verify['NOISE'] = bool(stats['NOISE'] <= np.sqrt(stats['MEAN'])) 

120 

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

122 if verify['SUCCESS'] is False: 

123 success = False 

124 

125 verifyStats[ampName] = verify 

126 

127 verifyDet = {} 

128 detStats = statisticsDict['DET'] 

129 

130 # DMTN-101 Test 10.Y: confirm intra-chip scatter is small. 

131 verifyDet['SCATTER'] = bool(detStats['SCATTER']/detStats['MEAN'] <= 0.05) 

132 

133 verifyDet['SUCCESS'] = bool(np.all(list(verifyDet.values()))) 

134 if verifyDet['SUCCESS'] is False: 

135 success = False 

136 

137 return {'AMP': verifyStats, 'DET': verifyDet}, bool(success) 

138 

139 def repackStats(self, statisticsDict, dimensions): 

140 # docstring inherited 

141 rows = {} 

142 rowList = [] 

143 matrixRowList = None 

144 

145 if self.config.useIsrStatistics: 

146 mjd = statisticsDict["ISR"]["MJD"] 

147 else: 

148 mjd = np.nan 

149 

150 print(dimensions) 

151 rowBase = { 

152 "instrument": dimensions["instrument"], 

153 "exposure": dimensions["exposure"], 

154 "detector": dimensions["detector"], 

155 "physical_filter": dimensions["physical_filter"], 

156 "mjd": mjd, 

157 } 

158 

159 # AMP results: 

160 for ampName, stats in statisticsDict["AMP"].items(): 

161 rows[ampName] = {} 

162 rows[ampName].update(rowBase) 

163 rows[ampName]["amplifier"] = ampName 

164 for key, value in stats.items(): 

165 rows[ampName][f"{self.config.stageName}_{key}"] = value 

166 

167 # VERIFY results 

168 for ampName, stats in statisticsDict["VERIFY"]["AMP"].items(): 

169 for key, value in stats.items(): 

170 rows[ampName][f"{self.config.stageName}_VERIFY_{key}"] = value 

171 

172 # METADATA results 

173 # DET results 

174 rows['detector'] = rowBase 

175 for testName, value in statisticsDict["DET"].items(): 

176 verifyDict = statisticsDict["VERIFY"]["DET"] 

177 rows['detector'][f"{self.config.stageName}_DET_{testName}"] = value 

178 if testName in verifyDict: 

179 rows['detector'][f"{self.config.stageName}_DET_VERIFY_{testName}"] = verifyDict[testName] 

180 

181 # ISR results 

182 if self.config.useIsrStatistics and "ISR" in statisticsDict: 

183 for ampName, stats in statisticsDict["ISR"]["CALIBDIST"].items(): 

184 for level in self.config.expectedDistributionLevels: 

185 key = f"LSST CALIB {self.config.stageName.upper()} {ampName} DISTRIBUTION {level}-PCT" 

186 rows[ampName][f"{self.config.stageName}_FLAT_DIST_{level}_PCT"] = stats[key] 

187 

188 # pack final list 

189 for ampName, stats in rows.items(): 

190 rowList.append(stats) 

191 

192 return rowList, matrixRowList 

193 

194 

195class CpVerifyFlatExpMergeConfig(CpVerifyExpMergeByFilterConfig): 

196 """Inherits from base CpVerifyExpMergeConfig 

197 """ 

198 

199 def setDefaults(self): 

200 super().setDefaults() 

201 self.statKeywords = { 

202 'EXPOSURE_SCATTER': 'STDEV', # noqa F841 

203 } 

204 

205 

206class CpVerifyFlatExpMergeTask(CpVerifyExpMergeByFilterTask): 

207 """Inherits from base CpVerifyExpMergeTask 

208 """ 

209 ConfigClass = CpVerifyFlatExpMergeConfig 

210 _DefaultName = 'cpVerifyFlatExpMerge' 

211 

212 def calcStatistics(self, statisticsDictionary): 

213 """Calculate exposure level statistics based on the existing 

214 per-amplifier and per-detector measurements. 

215 

216 Parameters 

217 ---------- 

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

219 Dictionary of measured statistics. The top level 

220 dictionary is keyed on the detector names, and contains 

221 the measured statistics from the per-detector 

222 measurements. 

223 

224 Returns 

225 ------- 

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

227 A dictionary of the statistics measured and their values. 

228 """ 

229 detectorMeans = [] 

230 for detName, stats in statisticsDictionary.items(): 

231 # Get detector stats: 

232 detectorMeans.append(stats['DET']['MEAN']) 

233 

234 return {'SCATTER': float(np.std(detectorMeans))} 

235 

236 def verify(self, detectorStatistics, statisticsDictionary): 

237 """Verify if the measured statistics meet the verification criteria. 

238 

239 Parameters 

240 ---------- 

241 detectorStatistics : `dict` [`str`, `dict` [`str`, scalar]] 

242 Merged set of input detector level statistics. 

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

244 Dictionary of measured statistics. The inner dictionary 

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

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

247 the mostly likely types). 

248 

249 Returns 

250 ------- 

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

252 A dictionary indexed by the amplifier name, containing 

253 dictionaries of the verification criteria. 

254 success : `bool` 

255 A boolean indicating if all tests have passed. 

256 

257 """ 

258 verifyStats = {} 

259 success = True 

260 

261 # DMTN-101 Test 10.Z: confirm inter-chip scatter is small. 

262 verifyStats['SCATTER'] = bool(statisticsDictionary['EXP']['SCATTER'] <= 0.05) 

263 

264 success = bool(np.all(list(verifyStats.values()))) 

265 

266 return {'EXP': verifyStats}, bool(success) 

267 

268 def pack(self, statisticsDict, dimensions, outKey): 

269 """Repack information into flat tables. 

270 

271 Parameters 

272 ---------- 

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

274 Dictionary of measured statistics. The inner dictionary 

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

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

277 the mostly likely types). 

278 dimensions : `dict` 

279 Dictionary of input dimensions. 

280 outKey : `str` 

281 Key to use to access the data to pack. 

282 

283 Returns 

284 ------- 

285 outputResults : `list` [`dict`] 

286 A list of rows to add to the output table. 

287 outputMatrix : `list` [`dict`] 

288 A list of rows to add to the output matrix. 

289 """ 

290 rowList = [] 

291 matrixRowList = None 

292 

293 # We can only do stats if we only have one thing. 

294 rowBase = { 

295 "instrument": dimensions[0]["instrument"], 

296 "exposure": dimensions[0]["exposure"], 

297 "detector": dimensions[0]["detector"], 

298 } 

299 

300 # This only needs to add the new results. 

301 stats = statisticsDict[outKey] 

302 verify = statisticsDict["VERIFY"][outKey] 

303 

304 for test, value in stats.items(): 

305 rowBase[f"{self.config.stageName}_{test}"] = value 

306 rowBase[f"{self.config.stageName}_VERIFY_{test}"] = verify[test] 

307 

308 # pack final list 

309 rowList.append(rowBase) 

310 

311 return rowList, matrixRowList