Coverage for python/lsst/cp/verify/mergeResults.py: 20%

96 statements  

« prev     ^ index     » next       coverage.py v7.2.1, created at 2023-03-12 03:58 -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 lsst.pipe.base as pipeBase 

22import lsst.pipe.base.connectionTypes as cT 

23import lsst.pex.config as pexConfig 

24 

25 

26__all__ = ['CpVerifyExpMergeConfig', 'CpVerifyExpMergeTask', 

27 'CpVerifyRunMergeConfig', 'CpVerifyRunMergeTask'] 

28 

29 

30class CpVerifyExpMergeConnections(pipeBase.PipelineTaskConnections, 

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

32 defaultTemplates={}): 

33 inputStats = cT.Input( 

34 name="detectorStats", 

35 doc="Input statistics to merge.", 

36 storageClass="StructuredDataDict", 

37 dimensions=["instrument", "exposure", "detector"], 

38 multiple=True, 

39 ) 

40 camera = cT.PrerequisiteInput( 

41 name="camera", 

42 storageClass="Camera", 

43 doc="Input camera.", 

44 dimensions=["instrument", ], 

45 isCalibration=True, 

46 ) 

47 

48 outputStats = cT.Output( 

49 name="exposureStats", 

50 doc="Output statistics.", 

51 storageClass="StructuredDataDict", 

52 dimensions=["instrument", "exposure"], 

53 ) 

54 

55 

56class CpVerifyExpMergeConfig(pipeBase.PipelineTaskConfig, 

57 pipelineConnections=CpVerifyExpMergeConnections): 

58 """Configuration parameters for exposure stats merging. 

59 """ 

60 exposureStatKeywords = pexConfig.DictField( 

61 keytype=str, 

62 itemtype=str, 

63 doc="Dictionary of statistics to run on the set of detector values. The key should be the test " 

64 "name to record in the output, and the value should be the `lsst.afw.math` statistic name string.", 

65 default={}, 

66 ) 

67 

68 

69class CpVerifyExpMergeTask(pipeBase.PipelineTask, pipeBase.CmdLineTask): 

70 """Merge statistics from detectors together. 

71 """ 

72 ConfigClass = CpVerifyExpMergeConfig 

73 _DefaultName = 'cpVerifyExpMerge' 

74 

75 def runQuantum(self, butlerQC, inputRefs, outputRefs): 

76 inputs = butlerQC.get(inputRefs) 

77 

78 dimensions = [exp.dataId.byName() for exp in inputRefs.inputStats] 

79 inputs['inputDims'] = dimensions 

80 

81 outputs = self.run(**inputs) 

82 butlerQC.put(outputs, outputRefs) 

83 

84 def run(self, inputStats, camera, inputDims): 

85 """Merge statistics. 

86 

87 Parameters 

88 ---------- 

89 inputStats : `list` [`dict`] 

90 Measured statistics for a detector (from 

91 CpVerifyStatsTask). 

92 camera : `lsst.afw.cameraGeom.Camera` 

93 The camera geometry for this exposure. 

94 inputDims : `list` [`dict`] 

95 List of dictionaries of input data dimensions/values. 

96 Each list entry should contain: 

97 

98 ``"exposure"`` 

99 exposure id value (`int`) 

100 ``"detector"`` 

101 detector id value (`int`) 

102 

103 Returns 

104 ------- 

105 outputStats : `dict` 

106 Merged full exposure statistics. 

107 

108 See Also 

109 -------- 

110 lsst.cp.verify.CpVerifyStatsTask 

111 

112 Notes 

113 ----- 

114 The outputStats should have a yaml representation of the form: 

115 

116 DET: 

117 DetName1: 

118 FAILURES: 

119 - TEST_NAME 

120 STAT: value 

121 STAT2: value2 

122 DetName2: 

123 VERIFY: 

124 TEST: boolean 

125 TEST2: boolean 

126 SUCCESS: boolean 

127 """ 

128 outputStats = {} 

129 success = True 

130 

131 mergedStats = {} 

132 for detStats, dimensions in zip(inputStats, inputDims): 

133 detId = dimensions['detector'] 

134 detName = camera[detId].getName() 

135 calcStats = {} 

136 

137 mergedStats[detName] = detStats 

138 

139 if detStats['SUCCESS'] is True: 

140 calcStats['SUCCESS'] = True 

141 else: 

142 calcStats['SUCCESS'] = False 

143 calcStats['FAILURES'] = list() 

144 success = False 

145 # See if the detector failed 

146 if 'DET' in detStats['VERIFY']: 

147 for testName, testResult in detStats['VERIFY']['DET'].items(): 

148 if testResult is False: 

149 calcStats['FAILURES'].append(testName) 

150 # See if the catalog failed 

151 if 'CATALOG' in detStats['VERIFY']: 

152 for testName, testResult in detStats['VERIFY']['CATALOG'].items(): 

153 if testResult is False: 

154 calcStats['FAILURES'].append(testName) 

155 # See if an amplifier failed 

156 for ampName, ampStats in detStats['VERIFY']['AMP'].items(): 

157 ampSuccess = ampStats.pop('SUCCESS') 

158 if not ampSuccess: 

159 for testName, testResult in ampStats.items(): 

160 if testResult is False: 

161 calcStats['FAILURES'].append(ampName + " " + testName) 

162 

163 outputStats[detName] = calcStats 

164 

165 exposureSuccess = True 

166 if len(self.config.exposureStatKeywords): 

167 outputStats['EXP'] = self.exposureStatistics(mergedStats) 

168 outputStats['VERIFY'], exposureSuccess = self.verify(mergedStats, outputStats) 

169 

170 outputStats['SUCCESS'] = success & exposureSuccess 

171 

172 return pipeBase.Struct( 

173 outputStats=outputStats, 

174 ) 

175 

176 def exposureStatistics(self, statisticsDict): 

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

178 per-amplifier and per-detector measurements. 

179 

180 Parameters 

181 ---------- 

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

183 Dictionary of measured statistics. The top level 

184 dictionary is keyed on the detector names, and contains 

185 the measured statistics from the per-detector 

186 measurements. 

187 

188 Returns 

189 ------- 

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

191 A dictionary of the statistics measured and their values. 

192 """ 

193 raise NotImplementedError("Subclasses must implement verification criteria.") 

194 

195 def verify(self, detectorStatistics, statisticsDictionary): 

196 

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

198 

199 Parameters 

200 ---------- 

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

202 Merged set of input detector level statistics. 

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

204 Dictionary of measured statistics. The inner dictionary 

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

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

207 the mostly likely types). 

208 

209 Returns 

210 ------- 

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

212 A dictionary indexed by the amplifier name, containing 

213 dictionaries of the verification criteria. 

214 success : `bool` 

215 A boolean indicating if all tests have passed. 

216 

217 Raises 

218 ------ 

219 NotImplementedError : 

220 This method must be implemented by the calibration-type 

221 subclass. 

222 """ 

223 raise NotImplementedError("Subclasses must implement verification criteria.") 

224 

225 

226class CpVerifyRunMergeConnections(pipeBase.PipelineTaskConnections, 

227 dimensions={"instrument"}, 

228 defaultTemplates={}): 

229 inputStats = cT.Input( 

230 name="exposureStats", 

231 doc="Input statistics to merge.", 

232 storageClass="StructuredDataDict", 

233 dimensions=["instrument", "exposure"], 

234 multiple=True, 

235 ) 

236 

237 outputStats = cT.Output( 

238 name="runStats", 

239 doc="Output statistics.", 

240 storageClass="StructuredDataDict", 

241 dimensions=["instrument"], 

242 ) 

243 

244 

245class CpVerifyRunMergeConfig(pipeBase.PipelineTaskConfig, 

246 pipelineConnections=CpVerifyRunMergeConnections): 

247 """Configuration paramters for exposure stats merging. 

248 """ 

249 runStatKeywords = pexConfig.DictField( 

250 keytype=str, 

251 itemtype=str, 

252 doc="Dictionary of statistics to run on the set of exposure values. The key should be the test " 

253 "name to record in the output, and the value should be the `lsst.afw.math` statistic name string.", 

254 default={}, 

255 ) 

256 

257 

258class CpVerifyRunMergeTask(pipeBase.PipelineTask, pipeBase.CmdLineTask): 

259 """Merge statistics from detectors together. 

260 """ 

261 ConfigClass = CpVerifyRunMergeConfig 

262 _DefaultName = 'cpVerifyRunMerge' 

263 

264 def runQuantum(self, butlerQC, inputRefs, outputRefs): 

265 inputs = butlerQC.get(inputRefs) 

266 

267 dimensions = [exp.dataId.byName() for exp in inputRefs.inputStats] 

268 inputs['inputDims'] = dimensions 

269 

270 outputs = self.run(**inputs) 

271 butlerQC.put(outputs, outputRefs) 

272 

273 def run(self, inputStats, inputDims): 

274 """Merge statistics. 

275 

276 Parameters 

277 ---------- 

278 inputStats : `list` [`dict`] 

279 Measured statistics for a detector. 

280 inputDims : `list` [`dict`] 

281 List of dictionaries of input data dimensions/values. 

282 Each list entry should contain: 

283 

284 ``"exposure"`` 

285 exposure id value (`int`) 

286 

287 Returns 

288 ------- 

289 outputStats : `dict` 

290 Merged full exposure statistics. 

291 

292 Notes 

293 ----- 

294 The outputStats should have a yaml representation as follows. 

295 

296 VERIFY: 

297 ExposureId1: 

298 VERIFY_MEAN: boolean 

299 VERIFY_SIGMA: boolean 

300 ExposureId2: 

301 [...] 

302 MEAN_UNIMODAL: boolean 

303 SIGMA_UNIMODAL: boolean 

304 """ 

305 outputStats = {} 

306 success = True 

307 for expStats, dimensions in zip(inputStats, inputDims): 

308 expId = dimensions['exposure'] 

309 calcStats = {} 

310 

311 expSuccess = expStats.pop('SUCCESS') 

312 if expSuccess: 

313 calcStats['SUCCESS'] = True 

314 else: 

315 calcStats['FAILURES'] = list() 

316 success = False 

317 for detName, detStats in expStats.items(): 

318 detSuccess = detStats.pop('SUCCESS') 

319 if not detSuccess: 

320 for testName in expStats[detName]['FAILURES']: 

321 calcStats['FAILURES'].append(detName + " " + testName) 

322 

323 outputStats[expId] = calcStats 

324 

325 runSuccess = True 

326 if len(self.config.runStatKeywords): 

327 outputStats['VERIFY'], runSuccess = self.verify(outputStats) 

328 

329 outputStats['SUCCESS'] = success & runSuccess 

330 

331 return pipeBase.Struct( 

332 outputStats=outputStats, 

333 ) 

334 

335 def verify(self, statisticsDictionary): 

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

337 

338 Parameters 

339 ---------- 

340 statisticsDictionary : `dict` [`str`, `dict`], 

341 Dictionary of measured statistics. The inner dictionary 

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

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

344 the mostly likely types). 

345 

346 Returns 

347 ------- 

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

349 A dictionary indexed by the amplifier name, containing 

350 dictionaries of the verification criteria. 

351 success : `bool` 

352 A boolean indicating if all tests have passed. 

353 

354 Raises 

355 ------ 

356 NotImplementedError : 

357 This method must be implemented by the calibration-type 

358 subclass. 

359 

360 """ 

361 raise NotImplementedError("Subclasses must implement verification criteria.")