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

173 statements  

« prev     ^ index     » next       coverage.py v7.3.3, created at 2023-12-15 14:02 +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 math 

22 

23import lsst.afw.geom as afwGeom 

24import lsst.afw.math as afwMath 

25import lsst.pex.config as pexConfig 

26import lsst.pex.exceptions as pexException 

27import lsst.pipe.base as pipeBase 

28import lsst.pipe.base.connectionTypes as cT 

29import lsst.meas.algorithms as measAlg 

30 

31from lsst.ip.isr.vignette import maskVignettedRegion 

32from lsst.pipe.tasks.repair import RepairTask 

33from .utils import mergeStatDict 

34 

35 

36__all__ = ["CpVerifyStatsConfig", "CpVerifyStatsTask"] 

37 

38 

39class CpVerifyStatsConnections( 

40 pipeBase.PipelineTaskConnections, 

41 dimensions={"instrument", "exposure", "detector"}, 

42 defaultTemplates={}, 

43): 

44 inputExp = cT.Input( 

45 name="postISRCCD", 

46 doc="Input exposure to calculate statistics for.", 

47 storageClass="Exposure", 

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

49 ) 

50 uncorrectedExp = cT.Input( 

51 name="uncorrectedExp", 

52 doc="Uncorrected input exposure to calculate statistics for.", 

53 storageClass="ExposureF", 

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

55 ) 

56 taskMetadata = cT.Input( 

57 name="isrTask_metadata", 

58 doc="Input task metadata to extract statistics from.", 

59 storageClass="TaskMetadata", 

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

61 ) 

62 inputCatalog = cT.Input( 

63 name="src", 

64 doc="Input catalog to calculate statistics for.", 

65 storageClass="SourceCatalog", 

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

67 ) 

68 uncorrectedCatalog = cT.Input( 

69 name="uncorrectedSrc", 

70 doc="Input catalog without correction applied.", 

71 storageClass="SourceCatalog", 

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

73 ) 

74 camera = cT.PrerequisiteInput( 

75 name="camera", 

76 storageClass="Camera", 

77 doc="Input camera.", 

78 dimensions=["instrument", ], 

79 isCalibration=True, 

80 ) 

81 outputStats = cT.Output( 

82 name="detectorStats", 

83 doc="Output statistics from cp_verify.", 

84 storageClass="StructuredDataDict", 

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

86 ) 

87 

88 def __init__(self, *, config=None): 

89 super().__init__(config=config) 

90 

91 if len(config.metadataStatKeywords) < 1: 

92 self.inputs.discard("taskMetadata") 

93 

94 if len(config.catalogStatKeywords) < 1: 

95 self.inputs.discard("inputCatalog") 

96 self.inputs.discard("uncorrectedCatalog") 

97 

98 if len(config.uncorrectedImageStatKeywords) < 1: 

99 self.inputs.discard("uncorrectedExp") 

100 

101 

102class CpVerifyStatsConfig( 

103 pipeBase.PipelineTaskConfig, pipelineConnections=CpVerifyStatsConnections 

104): 

105 """Configuration parameters for CpVerifyStatsTask.""" 

106 

107 maskNameList = pexConfig.ListField( 

108 dtype=str, 

109 doc="Mask list to exclude from statistics calculations.", 

110 default=["DETECTED", "BAD", "NO_DATA"], 

111 ) 

112 doVignette = pexConfig.Field( 

113 dtype=bool, 

114 doc="Mask vignetted regions?", 

115 default=False, 

116 ) 

117 doNormalize = pexConfig.Field( 

118 dtype=bool, 

119 doc="Normalize by exposure time?", 

120 default=False, 

121 ) 

122 

123 # Cosmic ray handling options. 

124 doCR = pexConfig.Field( 

125 dtype=bool, 

126 doc="Run CR rejection?", 

127 default=False, 

128 ) 

129 repair = pexConfig.ConfigurableField( 

130 target=RepairTask, 

131 doc="Repair task to use.", 

132 ) 

133 psfFwhm = pexConfig.Field( 

134 dtype=float, 

135 default=3.0, 

136 doc="Repair PSF FWHM (pixels).", 

137 ) 

138 psfSize = pexConfig.Field( 

139 dtype=int, 

140 default=21, 

141 doc="Repair PSF bounding-box size (pixels).", 

142 ) 

143 crGrow = pexConfig.Field( 

144 dtype=int, 

145 default=0, 

146 doc="Grow radius for CR (pixels).", 

147 ) 

148 

149 # Statistics options. 

150 useReadNoise = pexConfig.Field( 

151 dtype=bool, 

152 doc="Compare sigma against read noise?", 

153 default=True, 

154 ) 

155 numSigmaClip = pexConfig.Field( 

156 dtype=float, 

157 doc="Rejection threshold (sigma) for statistics clipping.", 

158 default=5.0, 

159 ) 

160 clipMaxIter = pexConfig.Field( 

161 dtype=int, 

162 doc="Max number of clipping iterations to apply.", 

163 default=3, 

164 ) 

165 

166 # Keywords and statistics to measure from different sources. 

167 imageStatKeywords = pexConfig.DictField( 

168 keytype=str, 

169 itemtype=str, 

170 doc="Image statistics to run on amplifier segments.", 

171 default={}, 

172 ) 

173 unmaskedImageStatKeywords = pexConfig.DictField( 

174 keytype=str, 

175 itemtype=str, 

176 doc="Image statistics to run on amplifier segments, ignoring masks.", 

177 default={}, 

178 ) 

179 uncorrectedImageStatKeywords = pexConfig.DictField( 

180 keytype=str, 

181 itemtype=str, 

182 doc="Uncorrected image statistics to run on amplifier segments.", 

183 default={}, 

184 ) 

185 crImageStatKeywords = pexConfig.DictField( 

186 keytype=str, 

187 itemtype=str, 

188 doc="Image statistics to run on CR cleaned amplifier segments.", 

189 default={}, 

190 ) 

191 normImageStatKeywords = pexConfig.DictField( 

192 keytype=str, 

193 itemtype=str, 

194 doc="Image statistics to run on expTime normalized amplifier segments.", 

195 default={}, 

196 ) 

197 metadataStatKeywords = pexConfig.DictField( 

198 keytype=str, 

199 itemtype=str, 

200 doc="Statistics to measure from the metadata of the exposure.", 

201 default={}, 

202 ) 

203 catalogStatKeywords = pexConfig.DictField( 

204 keytype=str, 

205 itemtype=str, 

206 doc="Statistics to measure from source catalogs of objects in the exposure.", 

207 default={}, 

208 ) 

209 detectorStatKeywords = pexConfig.DictField( 

210 keytype=str, 

211 itemtype=str, 

212 doc="Statistics to create for the full detector from the per-amplifier measurements.", 

213 default={}, 

214 ) 

215 

216 

217class CpVerifyStatsTask(pipeBase.PipelineTask): 

218 """Main statistic measurement and validation class. 

219 

220 This operates on a single (exposure, detector) pair, and is 

221 designed to be subclassed so specific calibrations can apply their 

222 own validation methods. 

223 """ 

224 

225 ConfigClass = CpVerifyStatsConfig 

226 _DefaultName = "cpVerifyStats" 

227 

228 def __init__(self, **kwargs): 

229 super().__init__(**kwargs) 

230 self.makeSubtask("repair") 

231 

232 def run( 

233 self, 

234 inputExp, 

235 camera, 

236 uncorrectedExp=None, 

237 taskMetadata=None, 

238 inputCatalog=None, 

239 uncorrectedCatalog=None, 

240 ): 

241 """Calculate quality statistics and verify they meet the requirements 

242 for a calibration. 

243 

244 Parameters 

245 ---------- 

246 inputExp : `lsst.afw.image.Exposure` 

247 The ISR processed exposure to be measured. 

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

249 The camera geometry for ``inputExp``. 

250 uncorrectedExp : `lsst.afw.image.Exposure` 

251 The alternate exposure to measure. 

252 taskMetadata : `lsst.pipe.base.TaskMetadata`, optional 

253 Task metadata containing additional statistics. 

254 inputCatalog : `lsst.afw.image.Table` 

255 The source catalog to measure. 

256 uncorrectedCatalog : `lsst.afw.image.Table` 

257 The alternate source catalog to measure. 

258 

259 Returns 

260 ------- 

261 result : `lsst.pipe.base.Struct` 

262 Result struct with components: 

263 - ``outputStats`` : `dict` 

264 The output measured statistics. 

265 

266 Notes 

267 ----- 

268 The outputStats should have a yaml representation of the form 

269 

270 AMP: 

271 Amp1: 

272 STAT: value 

273 STAT2: value2 

274 Amp2: 

275 Amp3: 

276 DET: 

277 STAT: value 

278 STAT2: value 

279 CATALOG: 

280 STAT: value 

281 STAT2: value 

282 VERIFY: 

283 DET: 

284 TEST: boolean 

285 CATALOG: 

286 TEST: boolean 

287 AMP: 

288 Amp1: 

289 TEST: boolean 

290 TEST2: boolean 

291 Amp2: 

292 Amp3: 

293 SUCCESS: boolean 

294 

295 """ 

296 outputStats = {} 

297 

298 if self.config.doVignette: 

299 polygon = inputExp.getInfo().getValidPolygon() 

300 maskVignettedRegion( 

301 inputExp, polygon, maskPlane="NO_DATA", vignetteValue=None, log=self.log 

302 ) 

303 

304 mask = inputExp.getMask() 

305 maskVal = mask.getPlaneBitMask(self.config.maskNameList) 

306 statControl = afwMath.StatisticsControl( 

307 self.config.numSigmaClip, self.config.clipMaxIter, maskVal 

308 ) 

309 

310 # This is wrapped below to check for config lengths, as we can 

311 # make a number of different image stats. 

312 outputStats["AMP"] = self.imageStatistics(inputExp, uncorrectedExp, statControl) 

313 

314 if len(self.config.metadataStatKeywords): 

315 # These are also defined on a amp-by-amp basis. 

316 outputStats["METADATA"] = self.metadataStatistics(inputExp, taskMetadata) 

317 else: 

318 outputStats["METADATA"] = {} 

319 

320 if len(self.config.catalogStatKeywords): 

321 outputStats["CATALOG"] = self.catalogStatistics( 

322 inputExp, inputCatalog, uncorrectedCatalog, statControl 

323 ) 

324 else: 

325 outputStats["CATALOG"] = {} 

326 if len(self.config.detectorStatKeywords): 

327 outputStats["DET"] = self.detectorStatistics( 

328 outputStats, statControl, inputExp, uncorrectedExp 

329 ) 

330 else: 

331 outputStats["DET"] = {} 

332 

333 outputStats["VERIFY"], outputStats["SUCCESS"] = self.verify( 

334 inputExp, outputStats 

335 ) 

336 

337 return pipeBase.Struct( 

338 outputStats=outputStats, 

339 ) 

340 

341 @staticmethod 

342 def _emptyAmpDict(exposure): 

343 """Construct empty dictionary indexed by amplifier names. 

344 

345 Parameters 

346 ---------- 

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

348 Exposure to extract detector from. 

349 

350 Returns 

351 ------- 

352 outputStatistics : `dict` [`str`, `dict`] 

353 A skeleton statistics dictionary. 

354 

355 Raises 

356 ------ 

357 RuntimeError : 

358 Raised if no detector can be found. 

359 """ 

360 outputStatistics = {} 

361 detector = exposure.getDetector() 

362 if detector is None: 

363 raise RuntimeError("No detector found in exposure!") 

364 

365 for amp in detector.getAmplifiers(): 

366 outputStatistics[amp.getName()] = {} 

367 

368 return outputStatistics 

369 

370 # Image measurement methods. 

371 def imageStatistics(self, exposure, uncorrectedExposure, statControl): 

372 """Measure image statistics for a number of simple image 

373 modifications. 

374 

375 Parameters 

376 ---------- 

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

378 Exposure containing the ISR processed data to measure. 

379 uncorrectedExposure: `lsst.afw.image.Exposure` 

380 Uncorrected exposure containing the ISR processed data to measure. 

381 statControl : `lsst.afw.math.StatisticsControl` 

382 Statistics control object with parameters defined by 

383 the config. 

384 

385 Returns 

386 ------- 

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

388 A dictionary indexed by the amplifier name, containing 

389 dictionaries of the statistics measured and their values. 

390 

391 """ 

392 outputStatistics = self._emptyAmpDict(exposure) 

393 

394 if len(self.config.imageStatKeywords): 

395 outputStatistics = mergeStatDict( 

396 outputStatistics, 

397 self.amplifierStats( 

398 exposure, self.config.imageStatKeywords, statControl 

399 ), 

400 ) 

401 if len(self.config.uncorrectedImageStatKeywords): 

402 outputStatistics = mergeStatDict( 

403 outputStatistics, 

404 self.amplifierStats( 

405 uncorrectedExposure, 

406 self.config.uncorrectedImageStatKeywords, 

407 statControl, 

408 ), 

409 ) 

410 if len(self.config.unmaskedImageStatKeywords): 

411 outputStatistics = mergeStatDict( 

412 outputStatistics, self.unmaskedImageStats(exposure) 

413 ) 

414 

415 if len(self.config.normImageStatKeywords): 

416 outputStatistics = mergeStatDict( 

417 outputStatistics, self.normalizedImageStats(exposure, statControl) 

418 ) 

419 

420 if len(self.config.crImageStatKeywords): 

421 outputStatistics = mergeStatDict( 

422 outputStatistics, self.crImageStats(exposure, statControl) 

423 ) 

424 

425 return outputStatistics 

426 

427 @staticmethod 

428 def _configHelper(keywordDict): 

429 """Helper to convert keyword dictionary to stat value. 

430 

431 Convert the string names in the keywordDict to the afwMath values. 

432 The statisticToRun is then the bitwise-or of that set. 

433 

434 Parameters 

435 ---------- 

436 keywordDict : `dict` [`str`, `str`] 

437 A dictionary of keys to use in the output results, with 

438 values the string name associated with the 

439 `lsst.afw.math.statistics.Property` to measure. 

440 

441 Returns 

442 ------- 

443 statisticToRun : `int` 

444 The merged `lsst.afw.math` statistics property. 

445 statAccessor : `dict` [`str`, `int`] 

446 Dictionary containing statistics property indexed by name. 

447 """ 

448 statisticToRun = 0 

449 statAccessor = {} 

450 for k, v in keywordDict.items(): 

451 statValue = afwMath.stringToStatisticsProperty(v) 

452 statisticToRun |= statValue 

453 statAccessor[k] = statValue 

454 

455 return statisticToRun, statAccessor 

456 

457 def metadataStatistics(self, exposure, taskMetadata): 

458 """Extract task metadata information for verification. 

459 

460 Parameters 

461 ---------- 

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

463 The exposure to measure. 

464 taskMetadata : `lsst.pipe.base.TaskMetadata` 

465 The metadata to extract values from. 

466 

467 Returns 

468 ------- 

469 ampStats : `dict` [`str`, `dict` [`str`, scalar]] 

470 A dictionary indexed by the amplifier name, containing 

471 dictionaries of the statistics measured and their values. 

472 """ 

473 metadataStats = {} 

474 keywordDict = self.config.metadataStatKeywords 

475 

476 if taskMetadata: 

477 for key, value in keywordDict.items(): 

478 if value == "AMP": 

479 metadataStats[key] = {} 

480 for ampIdx, amp in enumerate(exposure.getDetector()): 

481 ampName = amp.getName() 

482 expectedKey = f"{key} {ampName}" 

483 metadataStats[key][ampName] = None 

484 for name in taskMetadata: 

485 if expectedKey in taskMetadata[name]: 

486 metadataStats[key][ampName] = taskMetadata[name][ 

487 expectedKey 

488 ] 

489 else: 

490 # Assume it's detector-wide. 

491 expectedKey = key 

492 for name in taskMetadata: 

493 if expectedKey in taskMetadata[name]: 

494 metadataStats[key] = taskMetadata[name][expectedKey] 

495 return metadataStats 

496 

497 def amplifierStats(self, exposure, keywordDict, statControl, failAll=False): 

498 """Measure amplifier level statistics from the exposure. 

499 

500 Parameters 

501 ---------- 

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

503 The exposure to measure. 

504 keywordDict : `dict` [`str`, `str`] 

505 A dictionary of keys to use in the output results, with 

506 values the string name associated with the 

507 `lsst.afw.math.statistics.Property` to measure. 

508 statControl : `lsst.afw.math.StatisticsControl` 

509 Statistics control object with parameters defined by 

510 the config. 

511 failAll : `bool`, optional 

512 If True, all tests will be set as failed. 

513 

514 Returns 

515 ------- 

516 ampStats : `dict` [`str`, `dict` [`str`, scalar]] 

517 A dictionary indexed by the amplifier name, containing 

518 dictionaries of the statistics measured and their values. 

519 """ 

520 ampStats = {} 

521 statisticToRun, statAccessor = self._configHelper(keywordDict) 

522 # Measure stats on all amplifiers. 

523 for ampIdx, amp in enumerate(exposure.getDetector()): 

524 ampName = amp.getName() 

525 theseStats = {} 

526 ampExp = exposure.Factory(exposure, amp.getBBox()) 

527 stats = afwMath.makeStatistics( 

528 ampExp.getMaskedImage(), statisticToRun, statControl 

529 ) 

530 

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

532 theseStats[k] = stats.getValue(v) 

533 

534 if failAll: 

535 theseStats["FORCE_FAILURE"] = failAll 

536 ampStats[ampName] = theseStats 

537 

538 return ampStats 

539 

540 def unmaskedImageStats(self, exposure): 

541 """Measure amplifier level statistics on the exposure, including all 

542 pixels in the exposure, regardless of any mask planes set. 

543 

544 Parameters 

545 ---------- 

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

547 The exposure to measure. 

548 

549 Returns 

550 ------- 

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

552 A dictionary indexed by the amplifier name, containing 

553 dictionaries of the statistics measured and their values. 

554 """ 

555 noMaskStatsControl = afwMath.StatisticsControl( 

556 self.config.numSigmaClip, self.config.clipMaxIter, 0x0 

557 ) 

558 return self.amplifierStats( 

559 exposure, self.config.unmaskedImageStatKeywords, noMaskStatsControl 

560 ) 

561 

562 def normalizedImageStats(self, exposure, statControl): 

563 """Measure amplifier level statistics on the exposure after dividing 

564 by the exposure time. 

565 

566 Parameters 

567 ---------- 

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

569 The exposure to measure. 

570 statControl : `lsst.afw.math.StatisticsControl` 

571 Statistics control object with parameters defined by 

572 the config. 

573 

574 Returns 

575 ------- 

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

577 A dictionary indexed by the amplifier name, containing 

578 dictionaries of the statistics measured and their values. 

579 

580 Raises 

581 ------ 

582 RuntimeError : 

583 Raised if the exposure time cannot be used for normalization. 

584 """ 

585 scaledExposure = exposure.clone() 

586 exposureTime = scaledExposure.getInfo().getVisitInfo().getExposureTime() 

587 if exposureTime <= 0: 

588 raise RuntimeError(f"Invalid exposureTime {exposureTime}.") 

589 mi = scaledExposure.getMaskedImage() 

590 mi /= exposureTime 

591 

592 return self.amplifierStats( 

593 scaledExposure, self.config.normImageStatKeywords, statControl 

594 ) 

595 

596 def crImageStats(self, exposure, statControl): 

597 """Measure amplifier level statistics on the exposure, 

598 after running cosmic ray rejection. 

599 

600 Parameters 

601 ---------- 

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

603 The exposure to measure. 

604 statControl : `lsst.afw.math.StatisticsControl` 

605 Statistics control object with parameters defined by 

606 the config. 

607 

608 Returns 

609 ------- 

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

611 A dictionary indexed by the amplifier name, containing 

612 dictionaries of the statistics measured and their values. 

613 

614 """ 

615 crRejectedExp = exposure.clone() 

616 psf = measAlg.SingleGaussianPsf( 

617 self.config.psfSize, 

618 self.config.psfSize, 

619 self.config.psfFwhm / (2 * math.sqrt(2 * math.log(2))), 

620 ) 

621 crRejectedExp.setPsf(psf) 

622 try: 

623 self.repair.run(crRejectedExp, keepCRs=False) 

624 failAll = False 

625 except pexException.LengthError: 

626 self.log.warning( 

627 "Failure masking cosmic rays (too many found). Continuing." 

628 ) 

629 failAll = True 

630 

631 if self.config.crGrow > 0: 

632 crMask = crRejectedExp.getMaskedImage().getMask().getPlaneBitMask("CR") 

633 spans = afwGeom.SpanSet.fromMask(crRejectedExp.mask, crMask) 

634 spans = spans.dilated(self.config.crGrow) 

635 spans = spans.clippedTo(crRejectedExp.getBBox()) 

636 spans.setMask(crRejectedExp.mask, crMask) 

637 

638 return self.amplifierStats( 

639 crRejectedExp, self.config.crImageStatKeywords, statControl, failAll=failAll 

640 ) 

641 

642 # Methods that need to be implemented by the calibration-level subclasses. 

643 def catalogStatistics(self, exposure, catalog, uncorrectedCatalog, statControl): 

644 """Calculate statistics from a catalog. 

645 

646 Parameters 

647 ---------- 

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

649 The exposure to measure. 

650 catalog : `lsst.afw.table.Table` 

651 The catalog to measure. 

652 uncorrectedCatalog : `lsst.afw.table.Table` 

653 The alternate catalog to measure. 

654 statControl : `lsst.afw.math.StatisticsControl` 

655 Statistics control object with parameters defined by 

656 the config. 

657 

658 Returns 

659 ------- 

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

661 A dictionary indexed by the amplifier name, containing 

662 dictionaries of the statistics measured and their values. 

663 """ 

664 raise NotImplementedError( 

665 "Subclasses must implement catalog statistics method." 

666 ) 

667 

668 def detectorStatistics( 

669 self, statisticsDict, statControl, exposure=None, uncorrectedExposure=None 

670 ): 

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

672 per-amplifier measurements. 

673 

674 Parameters 

675 ---------- 

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

677 Dictionary of measured statistics. The inner dictionary 

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

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

680 the mostly likely types). 

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

682 Statistics control object with parameters defined by 

683 the config. 

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

685 Exposure containing the ISR-processed data to measure. 

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

687 uncorrected esposure (no defects) containing the 

688 ISR-processed data to measure. 

689 

690 Returns 

691 ------- 

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

693 A dictionary of the statistics measured and their values. 

694 

695 Raises 

696 ------ 

697 NotImplementedError : 

698 This method must be implemented by the calibration-type 

699 subclass. 

700 """ 

701 raise NotImplementedError( 

702 "Subclasses must implement detector statistics method." 

703 ) 

704 

705 def verify(self, exposure, statisticsDict): 

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

707 

708 Parameters 

709 ---------- 

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

711 The exposure the statistics are from. 

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

713 Dictionary of measured statistics. The inner dictionary 

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

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

716 the mostly likely types). 

717 

718 Returns 

719 ------- 

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

721 A dictionary indexed by the amplifier name, containing 

722 dictionaries of the verification criteria. 

723 success : `bool` 

724 A boolean indicating whether all tests have passed. 

725 

726 Raises 

727 ------ 

728 NotImplementedError : 

729 This method must be implemented by the calibration-type 

730 subclass. 

731 """ 

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