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

155 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-03-01 03:52 -0800

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 'CpVerifyCalibMergeConfig', 'CpVerifyCalibMergeTask'] 

31 

32 

33class CpVerifyExpMergeConnections(pipeBase.PipelineTaskConnections, 

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

35 defaultTemplates={}): 

36 inputStats = cT.Input( 

37 name="detectorStats", 

38 doc="Input statistics to merge.", 

39 storageClass="StructuredDataDict", 

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

41 multiple=True, 

42 ) 

43 camera = cT.PrerequisiteInput( 

44 name="camera", 

45 storageClass="Camera", 

46 doc="Input camera.", 

47 dimensions=["instrument", ], 

48 isCalibration=True, 

49 ) 

50 

51 outputStats = cT.Output( 

52 name="exposureStats", 

53 doc="Output statistics.", 

54 storageClass="StructuredDataDict", 

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

56 ) 

57 

58 

59class CpVerifyExpMergeConfig(pipeBase.PipelineTaskConfig, 

60 pipelineConnections=CpVerifyExpMergeConnections): 

61 """Configuration parameters for exposure stats merging. 

62 """ 

63 exposureStatKeywords = pexConfig.DictField( 

64 keytype=str, 

65 itemtype=str, 

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

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

68 default={}, 

69 ) 

70 

71 

72class CpVerifyExpMergeTask(pipeBase.PipelineTask): 

73 """Merge statistics from detectors together. 

74 """ 

75 ConfigClass = CpVerifyExpMergeConfig 

76 _DefaultName = 'cpVerifyExpMerge' 

77 

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

79 inputs = butlerQC.get(inputRefs) 

80 

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

82 inputs['inputDims'] = dimensions 

83 

84 outputs = self.run(**inputs) 

85 butlerQC.put(outputs, outputRefs) 

86 

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

88 """Merge statistics. 

89 

90 Parameters 

91 ---------- 

92 inputStats : `list` [`dict`] 

93 Measured statistics for a detector (from 

94 CpVerifyStatsTask). 

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

96 The camera geometry for this exposure. 

97 inputDims : `list` [`dict`] 

98 List of dictionaries of input data dimensions/values. 

99 Each list entry should contain: 

100 

101 ``"exposure"`` 

102 exposure id value (`int`) 

103 ``"detector"`` 

104 detector id value (`int`) 

105 

106 Returns 

107 ------- 

108 outputStats : `dict` 

109 Merged full exposure statistics. 

110 

111 See Also 

112 -------- 

113 lsst.cp.verify.CpVerifyStatsTask 

114 

115 Notes 

116 ----- 

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

118 

119 DET: 

120 DetName1: 

121 FAILURES: 

122 - TEST_NAME 

123 STAT: value 

124 STAT2: value2 

125 DetName2: 

126 VERIFY: 

127 TEST: boolean 

128 TEST2: boolean 

129 SUCCESS: boolean 

130 """ 

131 outputStats = {} 

132 success = True 

133 

134 mergedStats = {} 

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

136 detId = dimensions['detector'] 

137 detName = camera[detId].getName() 

138 calcStats = {} 

139 

140 mergedStats[detName] = detStats 

141 

142 if detStats['SUCCESS'] is True: 

143 calcStats['SUCCESS'] = True 

144 else: 

145 calcStats['SUCCESS'] = False 

146 calcStats['FAILURES'] = list() 

147 success = False 

148 # See if the detector failed 

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

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

151 if not detSuccess: 

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

153 if testResult is False: 

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

155 # See if the catalog failed 

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

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

158 if testResult is False: 

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

160 # See if an amplifier failed 

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

162 ampSuccess = ampStats.pop('SUCCESS') 

163 if not ampSuccess: 

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

165 if testResult is False: 

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

167 

168 outputStats[detName] = calcStats 

169 

170 exposureSuccess = True 

171 if len(self.config.exposureStatKeywords): 

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

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

174 

175 outputStats['SUCCESS'] = success & exposureSuccess 

176 

177 return pipeBase.Struct( 

178 outputStats=outputStats, 

179 ) 

180 

181 def exposureStatistics(self, statisticsDict): 

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

183 per-amplifier and per-detector measurements. 

184 

185 Parameters 

186 ---------- 

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

188 Dictionary of measured statistics. The top level 

189 dictionary is keyed on the detector names, and contains 

190 the measured statistics from the per-detector 

191 measurements. 

192 

193 Returns 

194 ------- 

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

196 A dictionary of the statistics measured and their values. 

197 """ 

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

199 

200 def verify(self, detectorStatistics, statisticsDictionary): 

201 

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

203 

204 Parameters 

205 ---------- 

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

207 Merged set of input detector level statistics. 

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

209 Dictionary of measured statistics. The inner dictionary 

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

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

212 the mostly likely types). 

213 

214 Returns 

215 ------- 

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

217 A dictionary indexed by the amplifier name, containing 

218 dictionaries of the verification criteria. 

219 success : `bool` 

220 A boolean indicating if all tests have passed. 

221 

222 Raises 

223 ------ 

224 NotImplementedError : 

225 This method must be implemented by the calibration-type 

226 subclass. 

227 """ 

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

229 

230 

231class CpVerifyRunMergeConnections(pipeBase.PipelineTaskConnections, 

232 dimensions={"instrument"}, 

233 defaultTemplates={}): 

234 inputStats = cT.Input( 

235 name="exposureStats", 

236 doc="Input statistics to merge.", 

237 storageClass="StructuredDataDict", 

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

239 multiple=True, 

240 ) 

241 

242 outputStats = cT.Output( 

243 name="runStats", 

244 doc="Output statistics.", 

245 storageClass="StructuredDataDict", 

246 dimensions=["instrument"], 

247 ) 

248 

249 

250class CpVerifyRunMergeConfig(pipeBase.PipelineTaskConfig, 

251 pipelineConnections=CpVerifyRunMergeConnections): 

252 """Configuration paramters for exposure stats merging. 

253 """ 

254 runStatKeywords = pexConfig.DictField( 

255 keytype=str, 

256 itemtype=str, 

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

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

259 default={}, 

260 ) 

261 

262 

263class CpVerifyRunMergeTask(pipeBase.PipelineTask): 

264 """Merge statistics from detectors together. 

265 """ 

266 ConfigClass = CpVerifyRunMergeConfig 

267 _DefaultName = 'cpVerifyRunMerge' 

268 

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

270 inputs = butlerQC.get(inputRefs) 

271 

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

273 inputs['inputDims'] = dimensions 

274 

275 outputs = self.run(**inputs) 

276 butlerQC.put(outputs, outputRefs) 

277 

278 def run(self, inputStats, inputDims): 

279 """Merge statistics. 

280 

281 Parameters 

282 ---------- 

283 inputStats : `list` [`dict`] 

284 Measured statistics for a detector. 

285 inputDims : `list` [`dict`] 

286 List of dictionaries of input data dimensions/values. 

287 Each list entry should contain: 

288 

289 ``"exposure"`` 

290 exposure id value (`int`) 

291 

292 Returns 

293 ------- 

294 outputStats : `dict` 

295 Merged full exposure statistics. 

296 

297 Notes 

298 ----- 

299 The outputStats should have a yaml representation as follows. 

300 

301 VERIFY: 

302 ExposureId1: 

303 VERIFY_TEST1: boolean 

304 VERIFY_TEST2: boolean 

305 ExposureId2: 

306 [...] 

307 TEST_VALUE: boolean 

308 TEST_VALUE2: boolean 

309 """ 

310 outputStats = {} 

311 success = True 

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

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

314 if expId is None: 

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

316 

317 calcStats = {} 

318 

319 expSuccess = expStats.pop('SUCCESS') 

320 if expSuccess: 

321 calcStats['SUCCESS'] = True 

322 else: 

323 calcStats['FAILURES'] = list() 

324 success = False 

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

326 detSuccess = detStats.pop('SUCCESS') 

327 if not detSuccess: 

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

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

330 

331 outputStats[expId] = calcStats 

332 

333 runSuccess = True 

334 if len(self.config.runStatKeywords): 

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

336 

337 outputStats['SUCCESS'] = success & runSuccess 

338 

339 return pipeBase.Struct( 

340 outputStats=outputStats, 

341 ) 

342 

343 def verify(self, statisticsDictionary): 

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

345 

346 Parameters 

347 ---------- 

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

349 Dictionary of measured statistics. The inner dictionary 

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

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

352 the mostly likely types). 

353 

354 Returns 

355 ------- 

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

357 A dictionary indexed by the amplifier name, containing 

358 dictionaries of the verification criteria. 

359 success : `bool` 

360 A boolean indicating if all tests have passed. 

361 

362 Raises 

363 ------ 

364 NotImplementedError : 

365 This method must be implemented by the calibration-type 

366 subclass. 

367 

368 """ 

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

370 

371 

372class CpVerifyVisitExpMergeConnections(pipeBase.PipelineTaskConnections, 

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

374 defaultTemplates={}): 

375 inputStats = cT.Input( 

376 name="detectorStats", 

377 doc="Input statistics to merge.", 

378 storageClass="StructuredDataDict", 

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

380 multiple=True, 

381 ) 

382 camera = cT.PrerequisiteInput( 

383 name="camera", 

384 storageClass="Camera", 

385 doc="Input camera.", 

386 dimensions=["instrument", ], 

387 isCalibration=True, 

388 ) 

389 

390 outputStats = cT.Output( 

391 name="exposureStats", 

392 doc="Output statistics.", 

393 storageClass="StructuredDataDict", 

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

395 ) 

396 

397 

398class CpVerifyVisitExpMergeConfig(CpVerifyExpMergeConfig, 

399 pipelineConnections=CpVerifyVisitExpMergeConnections): 

400 pass 

401 

402 

403class CpVerifyVisitExpMergeTask(CpVerifyExpMergeTask): 

404 """Merge visit based data.""" 

405 

406 ConfigClass = CpVerifyVisitExpMergeConfig 

407 _DefaultName = 'cpVerifyVisitExpMerge' 

408 

409 pass 

410 

411 

412class CpVerifyVisitRunMergeConnections(pipeBase.PipelineTaskConnections, 

413 dimensions={"instrument"}, 

414 defaultTemplates={}): 

415 inputStats = cT.Input( 

416 name="exposureStats", 

417 doc="Input statistics to merge.", 

418 storageClass="StructuredDataDict", 

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

420 multiple=True, 

421 ) 

422 

423 outputStats = cT.Output( 

424 name="runStats", 

425 doc="Output statistics.", 

426 storageClass="StructuredDataDict", 

427 dimensions=["instrument"], 

428 ) 

429 

430 

431class CpVerifyVisitRunMergeConfig(CpVerifyRunMergeConfig, 

432 pipelineConnections=CpVerifyVisitRunMergeConnections): 

433 pass 

434 

435 

436class CpVerifyVisitRunMergeTask(CpVerifyRunMergeTask): 

437 """Merge visit based data.""" 

438 

439 ConfigClass = CpVerifyVisitRunMergeConfig 

440 _DefaultName = 'cpVerifyVisitRunMerge' 

441 

442 pass 

443 

444 

445class CpVerifyCalibMergeConnections(pipeBase.PipelineTaskConnections, 

446 dimensions={"instrument"}, 

447 defaultTemplates={}): 

448 inputStats = cT.Input( 

449 name="exposureStats", 

450 doc="Input statistics to merge.", 

451 storageClass="StructuredDataDict", 

452 dimensions=["instrument", "detector"], 

453 multiple=True, 

454 ) 

455 

456 outputStats = cT.Output( 

457 name="exposureStats", 

458 doc="Output statistics.", 

459 storageClass="StructuredDataDict", 

460 dimensions=["instrument"], 

461 ) 

462 

463 

464class CpVerifyCalibMergeConfig(pipeBase.PipelineTaskConfig, 

465 pipelineConnections=CpVerifyCalibMergeConnections): 

466 """Configuration paramters for exposure stats merging. 

467 """ 

468 runStatKeywords = pexConfig.DictField( 

469 keytype=str, 

470 itemtype=str, 

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

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

473 default={}, 

474 ) 

475 

476 

477class CpVerifyCalibMergeTask(pipeBase.PipelineTask): 

478 """Merge statistics from detectors together. 

479 """ 

480 ConfigClass = CpVerifyCalibMergeConfig 

481 _DefaultName = 'cpVerifyCalibMerge' 

482 

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

484 inputs = butlerQC.get(inputRefs) 

485 

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

487 inputs['inputDims'] = dimensions 

488 

489 outputs = self.run(**inputs) 

490 butlerQC.put(outputs, outputRefs) 

491 

492 def run(self, inputStats, inputDims): 

493 """Merge statistics. 

494 

495 Parameters 

496 ---------- 

497 inputStats : `list` [`dict`] 

498 Measured statistics for a detector. 

499 inputDims : `list` [`dict`] 

500 List of dictionaries of input data dimensions/values. 

501 Each list entry should contain: 

502 

503 ``"detector"`` 

504 detector id value (`int`) 

505 

506 Returns 

507 ------- 

508 outputStats : `dict` 

509 Merged full exposure statistics. 

510 

511 Notes 

512 ----- 

513 The outputStats should have a yaml representation as follows. 

514 

515 Detector detId: 

516 FAILURES: 

517 - Detector detId TEST_NAME 

518 SUCCESS: boolean 

519 """ 

520 outputStats = {} 

521 success = True 

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

523 detId = dimensions['detector'] 

524 detName = f"Detector {detId}" 

525 calcStats = {} 

526 

527 detSuccess = detStats.pop('SUCCESS') 

528 if detSuccess: 

529 calcStats['SUCCESS'] = True 

530 else: 

531 calcStats['FAILURES'] = list() 

532 success = False 

533 for testName in detStats['VERIFY']: 

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

535 

536 outputStats[detName] = calcStats 

537 

538 runSuccess = True 

539 if len(self.config.runStatKeywords): 

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

541 

542 outputStats['SUCCESS'] = success & runSuccess 

543 

544 return pipeBase.Struct( 

545 outputStats=outputStats, 

546 ) 

547 

548 def verify(self, statisticsDictionary): 

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

550 

551 Parameters 

552 ---------- 

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

554 Dictionary of measured statistics. The inner dictionary 

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

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

557 the mostly likely types). 

558 

559 Returns 

560 ------- 

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

562 A dictionary indexed by the amplifier name, containing 

563 dictionaries of the verification criteria. 

564 success : `bool` 

565 A boolean indicating if all tests have passed. 

566 

567 Raises 

568 ------ 

569 NotImplementedError : 

570 This method must be implemented by the calibration-type 

571 subclass. 

572 

573 """ 

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