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

179 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-03-23 04:54 -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 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 isrStatistics = cT.Input( 

82 name="isrStatistics", 

83 storageClass="StructuredDataDict", 

84 doc="Pre-calculated statistics from IsrTask.", 

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

86 ) 

87 

88 outputStats = cT.Output( 

89 name="detectorStats", 

90 doc="Output statistics from cp_verify.", 

91 storageClass="StructuredDataDict", 

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

93 ) 

94 

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

96 super().__init__(config=config) 

97 

98 if len(config.metadataStatKeywords) < 1: 

99 self.inputs.discard("taskMetadata") 

100 

101 if len(config.catalogStatKeywords) < 1: 

102 self.inputs.discard("inputCatalog") 

103 self.inputs.discard("uncorrectedCatalog") 

104 

105 if len(config.uncorrectedImageStatKeywords) < 1: 

106 self.inputs.discard("uncorrectedExp") 

107 

108 if config.useIsrStatistics is not True: 

109 self.inputs.discard("isrStatistics") 

110 

111 

112class CpVerifyStatsConfig( 

113 pipeBase.PipelineTaskConfig, pipelineConnections=CpVerifyStatsConnections 

114): 

115 """Configuration parameters for CpVerifyStatsTask.""" 

116 

117 maskNameList = pexConfig.ListField( 

118 dtype=str, 

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

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

121 ) 

122 doVignette = pexConfig.Field( 

123 dtype=bool, 

124 doc="Mask vignetted regions?", 

125 default=False, 

126 ) 

127 doNormalize = pexConfig.Field( 

128 dtype=bool, 

129 doc="Normalize by exposure time?", 

130 default=False, 

131 ) 

132 

133 # Cosmic ray handling options. 

134 doCR = pexConfig.Field( 

135 dtype=bool, 

136 doc="Run CR rejection?", 

137 default=False, 

138 ) 

139 repair = pexConfig.ConfigurableField( 

140 target=RepairTask, 

141 doc="Repair task to use.", 

142 ) 

143 psfFwhm = pexConfig.Field( 

144 dtype=float, 

145 default=3.0, 

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

147 ) 

148 psfSize = pexConfig.Field( 

149 dtype=int, 

150 default=21, 

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

152 ) 

153 crGrow = pexConfig.Field( 

154 dtype=int, 

155 default=0, 

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

157 ) 

158 

159 # Statistics options. 

160 useReadNoise = pexConfig.Field( 

161 dtype=bool, 

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

163 default=True, 

164 ) 

165 numSigmaClip = pexConfig.Field( 

166 dtype=float, 

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

168 default=5.0, 

169 ) 

170 clipMaxIter = pexConfig.Field( 

171 dtype=int, 

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

173 default=3, 

174 ) 

175 

176 # Keywords and statistics to measure from different sources. 

177 imageStatKeywords = pexConfig.DictField( 

178 keytype=str, 

179 itemtype=str, 

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

181 default={}, 

182 ) 

183 unmaskedImageStatKeywords = pexConfig.DictField( 

184 keytype=str, 

185 itemtype=str, 

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

187 default={}, 

188 ) 

189 uncorrectedImageStatKeywords = pexConfig.DictField( 

190 keytype=str, 

191 itemtype=str, 

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

193 default={}, 

194 ) 

195 crImageStatKeywords = pexConfig.DictField( 

196 keytype=str, 

197 itemtype=str, 

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

199 default={}, 

200 ) 

201 normImageStatKeywords = pexConfig.DictField( 

202 keytype=str, 

203 itemtype=str, 

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

205 default={}, 

206 ) 

207 metadataStatKeywords = pexConfig.DictField( 

208 keytype=str, 

209 itemtype=str, 

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

211 default={}, 

212 ) 

213 catalogStatKeywords = pexConfig.DictField( 

214 keytype=str, 

215 itemtype=str, 

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

217 default={}, 

218 ) 

219 detectorStatKeywords = pexConfig.DictField( 

220 keytype=str, 

221 itemtype=str, 

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

223 default={}, 

224 ) 

225 useIsrStatistics = pexConfig.Field( 

226 dtype=bool, 

227 doc="Use statistics calculated by IsrTask?", 

228 default=False, 

229 ) 

230 

231 

232class CpVerifyStatsTask(pipeBase.PipelineTask): 

233 """Main statistic measurement and validation class. 

234 

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

236 designed to be subclassed so specific calibrations can apply their 

237 own validation methods. 

238 """ 

239 

240 ConfigClass = CpVerifyStatsConfig 

241 _DefaultName = "cpVerifyStats" 

242 

243 def __init__(self, **kwargs): 

244 super().__init__(**kwargs) 

245 self.makeSubtask("repair") 

246 

247 def run( 

248 self, 

249 inputExp, 

250 camera, 

251 isrStatistics=None, 

252 uncorrectedExp=None, 

253 taskMetadata=None, 

254 inputCatalog=None, 

255 uncorrectedCatalog=None, 

256 ): 

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

258 for a calibration. 

259 

260 Parameters 

261 ---------- 

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

263 The ISR processed exposure to be measured. 

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

265 The camera geometry for ``inputExp``. 

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

267 The alternate exposure to measure. 

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

269 Task metadata containing additional statistics. 

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

271 The source catalog to measure. 

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

273 The alternate source catalog to measure. 

274 

275 Returns 

276 ------- 

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

278 Result struct with components: 

279 - ``outputStats`` : `dict` 

280 The output measured statistics. 

281 

282 Notes 

283 ----- 

284 The outputStats should have a yaml representation of the form 

285 

286 AMP: 

287 Amp1: 

288 STAT: value 

289 STAT2: value2 

290 Amp2: 

291 Amp3: 

292 DET: 

293 STAT: value 

294 STAT2: value 

295 CATALOG: 

296 STAT: value 

297 STAT2: value 

298 VERIFY: 

299 DET: 

300 TEST: boolean 

301 CATALOG: 

302 TEST: boolean 

303 AMP: 

304 Amp1: 

305 TEST: boolean 

306 TEST2: boolean 

307 Amp2: 

308 Amp3: 

309 SUCCESS: boolean 

310 

311 """ 

312 outputStats = {} 

313 

314 if self.config.doVignette: 

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

316 maskVignettedRegion( 

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

318 ) 

319 

320 mask = inputExp.getMask() 

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

322 statControl = afwMath.StatisticsControl( 

323 self.config.numSigmaClip, self.config.clipMaxIter, maskVal 

324 ) 

325 

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

327 # make a number of different image stats. 

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

329 

330 if len(self.config.metadataStatKeywords): 

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

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

333 else: 

334 outputStats["METADATA"] = {} 

335 

336 if len(self.config.catalogStatKeywords): 

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

338 inputExp, inputCatalog, uncorrectedCatalog, statControl 

339 ) 

340 else: 

341 outputStats["CATALOG"] = {} 

342 if len(self.config.detectorStatKeywords): 

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

344 outputStats, statControl, inputExp, uncorrectedExp 

345 ) 

346 else: 

347 outputStats["DET"] = {} 

348 

349 if self.config.useIsrStatistics: 

350 outputStats["ISR"] = isrStatistics 

351 

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

353 inputExp, outputStats 

354 ) 

355 

356 return pipeBase.Struct( 

357 outputStats=outputStats, 

358 ) 

359 

360 @staticmethod 

361 def _emptyAmpDict(exposure): 

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

363 

364 Parameters 

365 ---------- 

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

367 Exposure to extract detector from. 

368 

369 Returns 

370 ------- 

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

372 A skeleton statistics dictionary. 

373 

374 Raises 

375 ------ 

376 RuntimeError : 

377 Raised if no detector can be found. 

378 """ 

379 outputStatistics = {} 

380 detector = exposure.getDetector() 

381 if detector is None: 

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

383 

384 for amp in detector.getAmplifiers(): 

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

386 

387 return outputStatistics 

388 

389 # Image measurement methods. 

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

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

392 modifications. 

393 

394 Parameters 

395 ---------- 

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

397 Exposure containing the ISR processed data to measure. 

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

399 Uncorrected exposure containing the ISR processed data to measure. 

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

401 Statistics control object with parameters defined by 

402 the config. 

403 

404 Returns 

405 ------- 

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

407 A dictionary indexed by the amplifier name, containing 

408 dictionaries of the statistics measured and their values. 

409 

410 """ 

411 outputStatistics = self._emptyAmpDict(exposure) 

412 

413 if len(self.config.imageStatKeywords): 

414 outputStatistics = mergeStatDict( 

415 outputStatistics, 

416 self.amplifierStats( 

417 exposure, self.config.imageStatKeywords, statControl 

418 ), 

419 ) 

420 if len(self.config.uncorrectedImageStatKeywords): 

421 outputStatistics = mergeStatDict( 

422 outputStatistics, 

423 self.amplifierStats( 

424 uncorrectedExposure, 

425 self.config.uncorrectedImageStatKeywords, 

426 statControl, 

427 ), 

428 ) 

429 if len(self.config.unmaskedImageStatKeywords): 

430 outputStatistics = mergeStatDict( 

431 outputStatistics, self.unmaskedImageStats(exposure) 

432 ) 

433 

434 if len(self.config.normImageStatKeywords): 

435 outputStatistics = mergeStatDict( 

436 outputStatistics, self.normalizedImageStats(exposure, statControl) 

437 ) 

438 

439 if len(self.config.crImageStatKeywords): 

440 outputStatistics = mergeStatDict( 

441 outputStatistics, self.crImageStats(exposure, statControl) 

442 ) 

443 

444 return outputStatistics 

445 

446 @staticmethod 

447 def _configHelper(keywordDict): 

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

449 

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

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

452 

453 Parameters 

454 ---------- 

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

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

457 values the string name associated with the 

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

459 

460 Returns 

461 ------- 

462 statisticToRun : `int` 

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

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

465 Dictionary containing statistics property indexed by name. 

466 """ 

467 statisticToRun = 0 

468 statAccessor = {} 

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

470 statValue = afwMath.stringToStatisticsProperty(v) 

471 statisticToRun |= statValue 

472 statAccessor[k] = statValue 

473 

474 return statisticToRun, statAccessor 

475 

476 def metadataStatistics(self, exposure, taskMetadata): 

477 """Extract task metadata information for verification. 

478 

479 Parameters 

480 ---------- 

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

482 The exposure to measure. 

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

484 The metadata to extract values from. 

485 

486 Returns 

487 ------- 

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

489 A dictionary indexed by the amplifier name, containing 

490 dictionaries of the statistics measured and their values. 

491 """ 

492 metadataStats = {} 

493 keywordDict = self.config.metadataStatKeywords 

494 

495 if taskMetadata: 

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

497 if value == "AMP": 

498 metadataStats[key] = {} 

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

500 ampName = amp.getName() 

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

502 metadataStats[key][ampName] = None 

503 for name in taskMetadata: 

504 if expectedKey in taskMetadata[name]: 

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

506 expectedKey 

507 ] 

508 else: 

509 # Assume it's detector-wide. 

510 expectedKey = key 

511 for name in taskMetadata: 

512 if expectedKey in taskMetadata[name]: 

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

514 return metadataStats 

515 

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

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

518 

519 Parameters 

520 ---------- 

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

522 The exposure to measure. 

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

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

525 values the string name associated with the 

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

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

528 Statistics control object with parameters defined by 

529 the config. 

530 failAll : `bool`, optional 

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

532 

533 Returns 

534 ------- 

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

536 A dictionary indexed by the amplifier name, containing 

537 dictionaries of the statistics measured and their values. 

538 """ 

539 ampStats = {} 

540 statisticToRun, statAccessor = self._configHelper(keywordDict) 

541 # Measure stats on all amplifiers. 

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

543 ampName = amp.getName() 

544 theseStats = {} 

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

546 stats = afwMath.makeStatistics( 

547 ampExp.getMaskedImage(), statisticToRun, statControl 

548 ) 

549 

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

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

552 

553 if failAll: 

554 theseStats["FORCE_FAILURE"] = failAll 

555 ampStats[ampName] = theseStats 

556 

557 return ampStats 

558 

559 def unmaskedImageStats(self, exposure): 

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

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

562 

563 Parameters 

564 ---------- 

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

566 The exposure to measure. 

567 

568 Returns 

569 ------- 

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

571 A dictionary indexed by the amplifier name, containing 

572 dictionaries of the statistics measured and their values. 

573 """ 

574 noMaskStatsControl = afwMath.StatisticsControl( 

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

576 ) 

577 return self.amplifierStats( 

578 exposure, self.config.unmaskedImageStatKeywords, noMaskStatsControl 

579 ) 

580 

581 def normalizedImageStats(self, exposure, statControl): 

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

583 by the exposure time. 

584 

585 Parameters 

586 ---------- 

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

588 The exposure to measure. 

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

590 Statistics control object with parameters defined by 

591 the config. 

592 

593 Returns 

594 ------- 

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

596 A dictionary indexed by the amplifier name, containing 

597 dictionaries of the statistics measured and their values. 

598 

599 Raises 

600 ------ 

601 RuntimeError : 

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

603 """ 

604 scaledExposure = exposure.clone() 

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

606 if exposureTime <= 0: 

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

608 mi = scaledExposure.getMaskedImage() 

609 mi /= exposureTime 

610 

611 return self.amplifierStats( 

612 scaledExposure, self.config.normImageStatKeywords, statControl 

613 ) 

614 

615 def crImageStats(self, exposure, statControl): 

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

617 after running cosmic ray rejection. 

618 

619 Parameters 

620 ---------- 

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

622 The exposure to measure. 

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

624 Statistics control object with parameters defined by 

625 the config. 

626 

627 Returns 

628 ------- 

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

630 A dictionary indexed by the amplifier name, containing 

631 dictionaries of the statistics measured and their values. 

632 

633 """ 

634 crRejectedExp = exposure.clone() 

635 psf = measAlg.SingleGaussianPsf( 

636 self.config.psfSize, 

637 self.config.psfSize, 

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

639 ) 

640 crRejectedExp.setPsf(psf) 

641 try: 

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

643 failAll = False 

644 except pexException.LengthError: 

645 self.log.warning( 

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

647 ) 

648 failAll = True 

649 

650 if self.config.crGrow > 0: 

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

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

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

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

655 spans.setMask(crRejectedExp.mask, crMask) 

656 

657 return self.amplifierStats( 

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

659 ) 

660 

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

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

663 """Calculate statistics from a catalog. 

664 

665 Parameters 

666 ---------- 

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

668 The exposure to measure. 

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

670 The catalog to measure. 

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

672 The alternate catalog to measure. 

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

674 Statistics control object with parameters defined by 

675 the config. 

676 

677 Returns 

678 ------- 

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

680 A dictionary indexed by the amplifier name, containing 

681 dictionaries of the statistics measured and their values. 

682 """ 

683 raise NotImplementedError( 

684 "Subclasses must implement catalog statistics method." 

685 ) 

686 

687 def detectorStatistics( 

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

689 ): 

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

691 per-amplifier measurements. 

692 

693 Parameters 

694 ---------- 

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

696 Dictionary of measured statistics. The inner dictionary 

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

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

699 the mostly likely types). 

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

701 Statistics control object with parameters defined by 

702 the config. 

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

704 Exposure containing the ISR-processed data to measure. 

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

706 uncorrected esposure (no defects) containing the 

707 ISR-processed data to measure. 

708 

709 Returns 

710 ------- 

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

712 A dictionary of the statistics measured and their values. 

713 

714 Raises 

715 ------ 

716 NotImplementedError : 

717 This method must be implemented by the calibration-type 

718 subclass. 

719 """ 

720 raise NotImplementedError( 

721 "Subclasses must implement detector statistics method." 

722 ) 

723 

724 def verify(self, exposure, statisticsDict): 

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

726 

727 Parameters 

728 ---------- 

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

730 The exposure the statistics are from. 

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

732 Dictionary of measured statistics. The inner dictionary 

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

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

735 the mostly likely types). 

736 

737 Returns 

738 ------- 

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

740 A dictionary indexed by the amplifier name, containing 

741 dictionaries of the verification criteria. 

742 success : `bool` 

743 A boolean indicating whether all tests have passed. 

744 

745 Raises 

746 ------ 

747 NotImplementedError : 

748 This method must be implemented by the calibration-type 

749 subclass. 

750 """ 

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