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

Shortcuts on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

119 statements  

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 'CpVerifyVisitExpMergeConfig', 'CpVerifyVisitExpMergeTask', 

29 'CpVerifyVisitRunMergeConfig', 'CpVerifyVisitRunMergeTask'] 

30 

31 

32class CpVerifyExpMergeConnections(pipeBase.PipelineTaskConnections, 

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

34 defaultTemplates={}): 

35 inputStats = cT.Input( 

36 name="detectorStats", 

37 doc="Input statistics to merge.", 

38 storageClass="StructuredDataDict", 

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

40 multiple=True, 

41 ) 

42 camera = cT.PrerequisiteInput( 

43 name="camera", 

44 storageClass="Camera", 

45 doc="Input camera.", 

46 dimensions=["instrument", ], 

47 isCalibration=True, 

48 ) 

49 

50 outputStats = cT.Output( 

51 name="exposureStats", 

52 doc="Output statistics.", 

53 storageClass="StructuredDataDict", 

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

55 ) 

56 

57 

58class CpVerifyExpMergeConfig(pipeBase.PipelineTaskConfig, 

59 pipelineConnections=CpVerifyExpMergeConnections): 

60 """Configuration parameters for exposure stats merging. 

61 """ 

62 exposureStatKeywords = pexConfig.DictField( 

63 keytype=str, 

64 itemtype=str, 

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

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

67 default={}, 

68 ) 

69 

70 

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

72 """Merge statistics from detectors together. 

73 """ 

74 ConfigClass = CpVerifyExpMergeConfig 

75 _DefaultName = 'cpVerifyExpMerge' 

76 

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

78 inputs = butlerQC.get(inputRefs) 

79 

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

81 inputs['inputDims'] = dimensions 

82 

83 outputs = self.run(**inputs) 

84 butlerQC.put(outputs, outputRefs) 

85 

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

87 """Merge statistics. 

88 

89 Parameters 

90 ---------- 

91 inputStats : `list` [`dict`] 

92 Measured statistics for a detector (from 

93 CpVerifyStatsTask). 

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

95 The camera geometry for this exposure. 

96 inputDims : `list` [`dict`] 

97 List of dictionaries of input data dimensions/values. 

98 Each list entry should contain: 

99 

100 ``"exposure"`` 

101 exposure id value (`int`) 

102 ``"detector"`` 

103 detector id value (`int`) 

104 

105 Returns 

106 ------- 

107 outputStats : `dict` 

108 Merged full exposure statistics. 

109 

110 See Also 

111 -------- 

112 lsst.cp.verify.CpVerifyStatsTask 

113 

114 Notes 

115 ----- 

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

117 

118 DET: 

119 DetName1: 

120 FAILURES: 

121 - TEST_NAME 

122 STAT: value 

123 STAT2: value2 

124 DetName2: 

125 VERIFY: 

126 TEST: boolean 

127 TEST2: boolean 

128 SUCCESS: boolean 

129 """ 

130 outputStats = {} 

131 success = True 

132 

133 mergedStats = {} 

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

135 detId = dimensions['detector'] 

136 detName = camera[detId].getName() 

137 calcStats = {} 

138 

139 mergedStats[detName] = detStats 

140 

141 if detStats['SUCCESS'] is True: 

142 calcStats['SUCCESS'] = True 

143 else: 

144 calcStats['SUCCESS'] = False 

145 calcStats['FAILURES'] = list() 

146 success = False 

147 # See if the detector failed 

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

149 detSuccess = detStats['VERIFY']['DET'].pop('SUCCESS', False) 

150 if not detSuccess: 

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

152 if testResult is False: 

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

154 # See if the catalog failed 

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

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

157 if testResult is False: 

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

159 # See if an amplifier failed 

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

161 ampSuccess = ampStats.pop('SUCCESS') 

162 if not ampSuccess: 

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

164 if testResult is False: 

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

166 

167 outputStats[detName] = calcStats 

168 

169 exposureSuccess = True 

170 if len(self.config.exposureStatKeywords): 

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

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

173 

174 outputStats['SUCCESS'] = success & exposureSuccess 

175 

176 return pipeBase.Struct( 

177 outputStats=outputStats, 

178 ) 

179 

180 def exposureStatistics(self, statisticsDict): 

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

182 per-amplifier and per-detector measurements. 

183 

184 Parameters 

185 ---------- 

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

187 Dictionary of measured statistics. The top level 

188 dictionary is keyed on the detector names, and contains 

189 the measured statistics from the per-detector 

190 measurements. 

191 

192 Returns 

193 ------- 

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

195 A dictionary of the statistics measured and their values. 

196 """ 

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

198 

199 def verify(self, detectorStatistics, statisticsDictionary): 

200 

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

202 

203 Parameters 

204 ---------- 

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

206 Merged set of input detector level statistics. 

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

208 Dictionary of measured statistics. The inner dictionary 

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

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

211 the mostly likely types). 

212 

213 Returns 

214 ------- 

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

216 A dictionary indexed by the amplifier name, containing 

217 dictionaries of the verification criteria. 

218 success : `bool` 

219 A boolean indicating if all tests have passed. 

220 

221 Raises 

222 ------ 

223 NotImplementedError : 

224 This method must be implemented by the calibration-type 

225 subclass. 

226 """ 

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

228 

229 

230class CpVerifyRunMergeConnections(pipeBase.PipelineTaskConnections, 

231 dimensions={"instrument"}, 

232 defaultTemplates={}): 

233 inputStats = cT.Input( 

234 name="exposureStats", 

235 doc="Input statistics to merge.", 

236 storageClass="StructuredDataDict", 

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

238 multiple=True, 

239 ) 

240 

241 outputStats = cT.Output( 

242 name="runStats", 

243 doc="Output statistics.", 

244 storageClass="StructuredDataDict", 

245 dimensions=["instrument"], 

246 ) 

247 

248 

249class CpVerifyRunMergeConfig(pipeBase.PipelineTaskConfig, 

250 pipelineConnections=CpVerifyRunMergeConnections): 

251 """Configuration paramters for exposure stats merging. 

252 """ 

253 runStatKeywords = pexConfig.DictField( 

254 keytype=str, 

255 itemtype=str, 

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

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

258 default={}, 

259 ) 

260 

261 

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

263 """Merge statistics from detectors together. 

264 """ 

265 ConfigClass = CpVerifyRunMergeConfig 

266 _DefaultName = 'cpVerifyRunMerge' 

267 

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

269 inputs = butlerQC.get(inputRefs) 

270 

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

272 inputs['inputDims'] = dimensions 

273 

274 outputs = self.run(**inputs) 

275 butlerQC.put(outputs, outputRefs) 

276 

277 def run(self, inputStats, inputDims): 

278 """Merge statistics. 

279 

280 Parameters 

281 ---------- 

282 inputStats : `list` [`dict`] 

283 Measured statistics for a detector. 

284 inputDims : `list` [`dict`] 

285 List of dictionaries of input data dimensions/values. 

286 Each list entry should contain: 

287 

288 ``"exposure"`` 

289 exposure id value (`int`) 

290 

291 Returns 

292 ------- 

293 outputStats : `dict` 

294 Merged full exposure statistics. 

295 

296 Notes 

297 ----- 

298 The outputStats should have a yaml representation as follows. 

299 

300 VERIFY: 

301 ExposureId1: 

302 VERIFY_MEAN: boolean 

303 VERIFY_SIGMA: boolean 

304 ExposureId2: 

305 [...] 

306 MEAN_UNIMODAL: boolean 

307 SIGMA_UNIMODAL: boolean 

308 """ 

309 outputStats = {} 

310 success = True 

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

312 expId = dimensions.get('exposure', dimensions.get('visit', None)) 

313 if expId is None: 

314 raise RuntimeError("Could not identify the exposure from %s", dimensions) 

315 

316 calcStats = {} 

317 

318 expSuccess = expStats.pop('SUCCESS') 

319 if expSuccess: 

320 calcStats['SUCCESS'] = True 

321 else: 

322 calcStats['FAILURES'] = list() 

323 success = False 

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

325 detSuccess = detStats.pop('SUCCESS') 

326 if not detSuccess: 

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

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

329 

330 outputStats[expId] = calcStats 

331 

332 runSuccess = True 

333 if len(self.config.runStatKeywords): 

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

335 

336 outputStats['SUCCESS'] = success & runSuccess 

337 

338 return pipeBase.Struct( 

339 outputStats=outputStats, 

340 ) 

341 

342 def verify(self, statisticsDictionary): 

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

344 

345 Parameters 

346 ---------- 

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

348 Dictionary of measured statistics. The inner dictionary 

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

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

351 the mostly likely types). 

352 

353 Returns 

354 ------- 

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

356 A dictionary indexed by the amplifier name, containing 

357 dictionaries of the verification criteria. 

358 success : `bool` 

359 A boolean indicating if all tests have passed. 

360 

361 Raises 

362 ------ 

363 NotImplementedError : 

364 This method must be implemented by the calibration-type 

365 subclass. 

366 

367 """ 

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

369 

370 

371class CpVerifyVisitExpMergeConnections(pipeBase.PipelineTaskConnections, 

372 dimensions={"instrument", "visit"}, 

373 defaultTemplates={}): 

374 inputStats = cT.Input( 

375 name="detectorStats", 

376 doc="Input statistics to merge.", 

377 storageClass="StructuredDataDict", 

378 dimensions=["instrument", "visit", "detector"], 

379 multiple=True, 

380 ) 

381 camera = cT.PrerequisiteInput( 

382 name="camera", 

383 storageClass="Camera", 

384 doc="Input camera.", 

385 dimensions=["instrument", ], 

386 isCalibration=True, 

387 ) 

388 

389 outputStats = cT.Output( 

390 name="exposureStats", 

391 doc="Output statistics.", 

392 storageClass="StructuredDataDict", 

393 dimensions=["instrument", "visit"], 

394 ) 

395 

396 

397class CpVerifyVisitExpMergeConfig(CpVerifyExpMergeConfig, 

398 pipelineConnections=CpVerifyVisitExpMergeConnections): 

399 pass 

400 

401 

402class CpVerifyVisitExpMergeTask(CpVerifyExpMergeTask): 

403 """Merge visit based data.""" 

404 

405 ConfigClass = CpVerifyVisitExpMergeConfig 

406 _DefaultName = 'cpVerifyVisitExpMerge' 

407 

408 pass 

409 

410 

411class CpVerifyVisitRunMergeConnections(pipeBase.PipelineTaskConnections, 

412 dimensions={"instrument"}, 

413 defaultTemplates={}): 

414 inputStats = cT.Input( 

415 name="exposureStats", 

416 doc="Input statistics to merge.", 

417 storageClass="StructuredDataDict", 

418 dimensions=["instrument", "visit"], 

419 multiple=True, 

420 ) 

421 

422 outputStats = cT.Output( 

423 name="runStats", 

424 doc="Output statistics.", 

425 storageClass="StructuredDataDict", 

426 dimensions=["instrument"], 

427 ) 

428 

429 

430class CpVerifyVisitRunMergeConfig(CpVerifyRunMergeConfig, 

431 pipelineConnections=CpVerifyVisitRunMergeConnections): 

432 pass 

433 

434 

435class CpVerifyVisitRunMergeTask(CpVerifyRunMergeTask): 

436 """Merge visit based data.""" 

437 

438 ConfigClass = CpVerifyVisitRunMergeConfig 

439 _DefaultName = 'cpVerifyVisitRunMerge' 

440 

441 pass