Coverage for python/lsst/cp/verify/repackStats.py: 22%

211 statements  

« prev     ^ index     » next       coverage.py v7.4.1, created at 2024-02-20 13:34 +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 numpy as np 

22 

23from astropy.table import Table 

24 

25import lsst.pipe.base as pipeBase 

26import lsst.pipe.base.connectionTypes as cT 

27import lsst.pex.config as pexConfig 

28 

29__all__ = [ 

30 "CpVerifyRepackInstrumentConnections", 

31 "CpVerifyRepackPhysicalFilterConnections", 

32 "CpVerifyRepackInstrumentConfig", 

33 "CpVerifyRepackPhysicalFilterConfig", 

34 "CpVerifyRepackBiasTask", 

35 "CpVerifyRepackDarkTask", 

36 "CpVerifyRepackFlatTask", 

37 "CpVerifyRepackDefectTask", 

38 "CpVerifyRepackPtcTask", 

39 "CpVerifyRepackBfkTask", 

40 "CpVerifyRepackCtiTask", 

41] 

42 

43 

44class CpVerifyRepackInstrumentConnections(pipeBase.PipelineTaskConnections, 

45 dimensions={"instrument"}, 

46 defaultTemplate={}): 

47 """Connections class for calibration statistics with only instrument 

48 dimension. 

49 """ 

50 detectorStats = cT.Input( 

51 name="detectorStats", 

52 doc="Input detector statistics.", 

53 storageClass="StructuredDataDict", 

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

55 multiple=True, 

56 ) 

57 exposureStats = cT.Input( 

58 name="exposureStats", 

59 doc="Input exposure statistics.", 

60 storageClass="StructuredDataDict", 

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

62 multiple=True, 

63 ) 

64 runStats = cT.Input( 

65 name="runStats", 

66 doc="Input Run statistics.", 

67 storageClass="StructuredDataDict", 

68 dimensions={"instrument"}, 

69 multiple=True, 

70 ) 

71 

72 outputCatalog = cT.Output( 

73 name="cpvCatalog", 

74 doc="Output merged catalog.", 

75 storageClass="ArrowAstropy", 

76 dimensions={"instrument"}, 

77 ) 

78 

79 

80class CpVerifyRepackInstrumentConfig(pipeBase.PipelineTaskConfig, 

81 pipelineConnections=CpVerifyRepackInstrumentConnections): 

82 

83 expectedDistributionLevels = pexConfig.ListField( 

84 dtype=float, 

85 doc="Percentile levels expected in the calibration header.", 

86 default=[0, 5, 16, 50, 84, 95, 100], 

87 ) 

88 

89 

90class CpVerifyRepackTask(pipeBase.PipelineTask): 

91 """Repack cpVerify statistics for analysis_tools. 

92 

93 This version is the base for calibrations with summary 

94 dimensions of instrument only. 

95 """ 

96 ConfigClass = CpVerifyRepackInstrumentConfig 

97 _DefaultName = "cpVerifyRepack" 

98 

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

100 inputs = butlerQC.get(inputRefs) 

101 

102 inputs["detectorDims"] = [dict(exp.dataId.required) for exp in inputRefs.detectorStats] 

103 inputs["exposureDims"] = [dict(exp.dataId.required) for exp in inputRefs.exposureStats] 

104 

105 outputs = self.run(**inputs) 

106 butlerQC.put(outputs, outputRefs) 

107 

108 def run(self, detectorStats, detectorDims, exposureStats, exposureDims, runStats): 

109 """ 

110 """ 

111 rowList = self.repack(detectorStats, detectorDims, exposureStats, exposureDims, runStats) 

112 catalog = Table(rowList) 

113 

114 return pipeBase.Struct( 

115 outputCatalog=catalog, 

116 ) 

117 

118 def repackDetStats(self, detectorStats, detectorDims): 

119 raise NotImplementedError("Repack needs to be defined by subclasses.") 

120 

121 def repackExpStats(self, exposureStats, exposureDims): 

122 # for expStats, expDims in zip(exposureStats, exposureDims): 

123 raise NotImplementedError("Repack needs to be defined by subclasses.") 

124 

125 def repackRunStats(self, runStats): 

126 # for runStats in runStats: 

127 raise NotImplementedError("Repack needs to be defined by subclasses.") 

128 

129 def repack(self, detectorStats, detectorDims, exposureStats, exposureDims, runStats): 

130 return self.repackDetStats(detectorStats, detectorDims) 

131 

132 

133class CpVerifyRepackBiasTask(CpVerifyRepackTask): 

134 stageName = "bias" 

135 

136 def repackDetStats(self, detectorStats, detectorDims): 

137 rowList = [] 

138 

139 for detStats, detDims in zip(detectorStats, detectorDims): 

140 row = {} 

141 instrument = detDims["instrument"] 

142 exposure = detDims["exposure"] 

143 detector = detDims["detector"] 

144 mjd = detStats["ISR"]["MJD"] 

145 

146 # Get amp stats 

147 # AMP {ampName} [CR_NOISE MEAN NOISE] value 

148 for ampName, stats in detStats["AMP"].items(): 

149 row[ampName] = { 

150 "instrument": instrument, 

151 "exposure": exposure, 

152 "mjd": mjd, 

153 "detector": detector, 

154 "amplifier": ampName, 

155 "biasMean": stats["MEAN"], 

156 "biasNoise": stats["NOISE"], 

157 "biasCrNoise": stats["CR_NOISE"] 

158 } 

159 # Get catalog stats CATALOG 

160 # Get detector stats DET 

161 # Get metadata stats 

162 # METADATA (RESIDUAL STDEV) {ampName} value 

163 for ampName, value in detStats["METADATA"]["RESIDUAL STDEV"].items(): 

164 row[ampName]["biasReadNoise"] = value 

165 

166 # Get verify stats 

167 for ampName, stats in detStats["VERIFY"]["AMP"].items(): 

168 row[ampName]["biasVerifyMean"] = stats["MEAN"] 

169 row[ampName]["biasVerifyNoise"] = stats["NOISE"] 

170 row[ampName]["biasVerifyCrNoise"] = stats["CR_NOISE"] 

171 row[ampName]["biasVerifyReadNoiseConsistent"] = stats["READ_NOISE_CONSISTENT"] 

172 

173 # Get isr stats 

174 for ampName, stats in detStats["ISR"]["CALIBDIST"].items(): 

175 for level in self.config.expectedDistributionLevels: 

176 key = f"LSST CALIB {self.stageName.upper()} {ampName} DISTRIBUTION {level}-PCT" 

177 row[ampName][f"biasDistribution_{level}"] = stats[key] 

178 

179 projStats = detStats["ISR"]["PROJECTION"] 

180 for ampName in projStats["AMP_HPROJECTION"].keys(): 

181 row[ampName]["biasSerialProfile"] = np.array(projStats["AMP_HPROJECTION"][ampName]) 

182 for ampName in projStats["AMP_VPROJECTION"].keys(): 

183 row[ampName]["biasParallelProfile"] = np.array(projStats["AMP_VPROJECTION"][ampName]) 

184 

185 # Create output table: 

186 for ampName, stats in row.items(): 

187 rowList.append(stats) 

188 

189 # We need all rows of biasParallelProfile and biasParallelProfile 

190 # to be the same length for serialization. Therefore, we pad 

191 # to the longest length. 

192 

193 maxSerialLen = 0 

194 maxParallelLen = 0 

195 

196 for row in rowList: 

197 if len(row["biasSerialProfile"]) > maxSerialLen: 

198 maxSerialLen = len(row["biasSerialProfile"]) 

199 if len(row["biasParallelProfile"]) > maxParallelLen: 

200 maxParallelLen = len(row["biasParallelProfile"]) 

201 

202 for row in rowList: 

203 if len(row["biasSerialProfile"]) < maxSerialLen: 

204 row["biasSerialProfile"] = np.pad( 

205 row["biasSerialProfile"], 

206 (0, maxSerialLen - len(row["biasSerialProfile"])), 

207 constant_values=np.nan, 

208 ) 

209 if len(row["biasParallelProfile"]) < maxParallelLen: 

210 row["biasParallelProfile"] = np.pad( 

211 row["biasParallelProfile"], 

212 (0, maxParallelLen - len(row["biasParallelProfile"])), 

213 constant_values=np.nan, 

214 ) 

215 

216 return rowList 

217 

218 

219class CpVerifyRepackDarkTask(CpVerifyRepackTask): 

220 stageName = "dark" 

221 

222 def repackDetStats(self, detectorStats, detectorDims): 

223 rowList = [] 

224 

225 for detStats, detDims in zip(detectorStats, detectorDims): 

226 row = {} 

227 instrument = detDims["instrument"] 

228 exposure = detDims["exposure"] 

229 detector = detDims["detector"] 

230 

231 # Get amp stats 

232 # AMP {ampName} [CR_NOISE MEAN NOISE] value 

233 for ampName, stats in detStats["AMP"].items(): 

234 row[ampName] = { 

235 "instrument": instrument, 

236 "exposure": exposure, 

237 "detector": detector, 

238 "amplifier": ampName, 

239 "darkMean": stats["MEAN"], 

240 "darkNoise": stats["NOISE"], 

241 "darkCrNoise": stats["CR_NOISE"] 

242 } 

243 # Get catalog stats 

244 # Get detector stats 

245 # Get metadata stats 

246 for ampName, value in detStats["METADATA"]["RESIDUAL STDEV"].items(): 

247 row[ampName]["darkReadNoise"] = value 

248 

249 # Get verify stats 

250 for ampName, stats in detStats["VERIFY"]["AMP"].items(): 

251 row[ampName]["darkVerifyMean"] = stats["MEAN"] 

252 row[ampName]["darkVerifyNoise"] = stats["NOISE"] 

253 row[ampName]["darkVerifyCrNoise"] = stats["CR_NOISE"] 

254 row[ampName]["darkVerifyReadNoiseConsistent"] = stats["READ_NOISE_CONSISTENT"] 

255 # Get isr stats 

256 for ampName, stats in detStats["ISR"]["CALIBDIST"].items(): 

257 for level in self.config.expectedDistributionLevels: 

258 key = f"LSST CALIB {self.stageName.upper()} {ampName} DISTRIBUTION {level}-PCT" 

259 row[ampName][f"darkDistribution_{level}"] = stats[key] 

260 

261 # Append to output 

262 for ampName, stats in row.items(): 

263 rowList.append(stats) 

264 

265 return rowList 

266 

267 

268class CpVerifyRepackPhysicalFilterConnections(pipeBase.PipelineTaskConnections, 

269 dimensions={"instrument", "physical_filter"}, 

270 defaultTemplate={}): 

271 """Connections class for calibration statistics with physical_filter 

272 (and instrument) dimensions. 

273 """ 

274 detectorStats = cT.Input( 

275 name="detectorStats", 

276 doc="Input detector statistics.", 

277 storageClass="StructuredDataDict", 

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

279 multiple=True, 

280 ) 

281 exposureStats = cT.Input( 

282 name="exposureStats", 

283 doc="Input exposure statistics.", 

284 storageClass="StructuredDataDict", 

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

286 multiple=True, 

287 ) 

288 runStats = cT.Input( 

289 name="runStats", 

290 doc="Input Run statistics.", 

291 storageClass="StructuredDataDict", 

292 dimensions={"instrument"}, 

293 multiple=True, 

294 ) 

295 

296 outputCatalog = cT.Output( 

297 name="cpvCatalog", 

298 doc="Output merged catalog.", 

299 storageClass="ArrowAstropy", 

300 dimensions={"instrument", "physical_filter"}, 

301 ) 

302 

303 

304class CpVerifyRepackPhysicalFilterConfig(pipeBase.PipelineTaskConfig, 

305 pipelineConnections=CpVerifyRepackPhysicalFilterConnections): 

306 expectedDistributionLevels = pexConfig.ListField( 

307 dtype=float, 

308 doc="Percentile levels expected in the calibration header.", 

309 default=[0, 5, 16, 50, 84, 95, 100], 

310 ) 

311 

312 

313class CpVerifyRepackFlatTask(CpVerifyRepackTask): 

314 ConfigClass = CpVerifyRepackPhysicalFilterConfig 

315 

316 stageName = "flat" 

317 

318 def repackDetStats(self, detectorStats, detectorDims): 

319 rowList = [] 

320 

321 for detStats, detDims in zip(detectorStats, detectorDims): 

322 row = {} 

323 instrument = detDims["instrument"] 

324 exposure = detDims["exposure"] 

325 detector = detDims["detector"] 

326 

327 # Get amp stats 

328 # AMP {ampName} [MEAN NOISE] value 

329 for ampName, stats in detStats["AMP"].items(): 

330 row[ampName] = { 

331 "instrument": instrument, 

332 "exposure": exposure, 

333 "detector": detector, 

334 "amplifier": ampName, 

335 "flatMean": stats["MEAN"], 

336 "flatNoise": stats["NOISE"], 

337 } 

338 # Get catalog stats CATALOG 

339 

340 # Get metadata stats 

341 # METADATA (RESIDUAL STDEV) {ampName} value 

342 

343 # Get verify stats 

344 for ampName, stats in detStats["VERIFY"]["AMP"].items(): 

345 row[ampName]["flatVerifyNoise"] = stats["NOISE"] 

346 # Get isr stats 

347 for ampName, stats in detStats["ISR"]["CALIBDIST"].items(): 

348 for level in self.config.expectedDistributionLevels: 

349 key = f"LSST CALIB {self.stageName.upper()} {ampName} DISTRIBUTION {level}-PCT" 

350 row[ampName][f"flatDistribution_{level}"] = stats[key] 

351 # Get detector stats 

352 # DET 

353 row["detector"] = {"instrument": instrument, 

354 "exposure": exposure, 

355 "detector": detector, 

356 "flatDetMean": detStats["DET"]["MEAN"], 

357 "flatDetScatter": detStats["DET"]["SCATTER"], 

358 } 

359 

360 # Append to output 

361 for ampName, stats in row.items(): 

362 rowList.append(stats) 

363 

364 return rowList 

365 

366 

367class CpVerifyRepackDefectTask(CpVerifyRepackTask): 

368 stageName = "defects" 

369 

370 def repackDetStats(self, detectorStats, detectorDims): 

371 rowList = [] 

372 for detStats, detDims in zip(detectorStats, detectorDims): 

373 row = {} 

374 instrument = detDims["instrument"] 

375 detector = detDims["detector"] 

376 

377 # Get amp stats 

378 for ampName, stats in detStats["AMP"].items(): 

379 row[ampName] = { 

380 "instrument": instrument, 

381 "detector": detector, 

382 "amplifier": ampName, 

383 } 

384 # Get catalog stats CATALOG 

385 # Get metadata stats METADATA 

386 # Get verify stats VERIFY 

387 # Get isr stats ISR 

388 nBadColumns = np.nan 

389 for ampName, stats in detStats["ISR"]["CALIBDIST"].items(): 

390 if ampName == "detector": 

391 nBadColumns = stats[ampName]["LSST CALIB DEFECTS N_BAD_COLUMNS"] 

392 else: 

393 key = f"LSST CALIB DEFECTS {ampName} N_HOT" 

394 row[ampName]["hotPixels"] = stats[ampName][key] 

395 key = f"LSST CALIB DEFECTS {ampName} N_COLD" 

396 row[ampName]["coldPixels"] = stats[ampName][key] 

397 # Get detector stats DET 

398 row["detector"] = {"instrument": instrument, 

399 "detector": detector, 

400 "nBadColumns": nBadColumns, 

401 } 

402 for ampName, stats in row.items(): 

403 rowList.append(stats) 

404 

405 return rowList 

406 

407 

408class CpVerifyRepackCtiTask(CpVerifyRepackTask): 

409 stageName = "cti" 

410 pass 

411 

412 

413class CpVerifyRepackBfkTask(CpVerifyRepackTask): 

414 stageName = "bfk" 

415 pass 

416 

417 

418class CpVerifyRepackNoExpConnections(pipeBase.PipelineTaskConnections, 

419 dimensions={"instrument"}, 

420 defaultTemplate={}): 

421 detectorStats = cT.Input( 

422 name="detectorStats", 

423 doc="Input detector statistics.", 

424 storageClass="StructuredDataDict", 

425 dimensions={"instrument", "detector"}, 

426 multiple=True, 

427 ) 

428 runStats = cT.Input( 

429 name="runStats", 

430 doc="Input Run statistics.", 

431 storageClass="StructuredDataDict", 

432 dimensions={"instrument"}, 

433 multiple=True, 

434 ) 

435 

436 outputCatalog = cT.Output( 

437 name="cpvCatalog", 

438 doc="Output merged catalog.", 

439 storageClass="ArrowAstropy", 

440 dimensions={"instrument"}, 

441 ) 

442 

443 

444class CpVerifyRepackNoExpConfig(pipeBase.PipelineTaskConfig, 

445 pipelineConnections=CpVerifyRepackNoExpConnections): 

446 

447 expectedDistributionLevels = pexConfig.ListField( 

448 dtype=float, 

449 doc="Percentile levels expected in the calibration header.", 

450 default=[0, 5, 16, 50, 84, 95, 100], 

451 ) 

452 

453 

454class CpVerifyRepackNoExpTask(CpVerifyRepackTask): 

455 """Repack cpVerify statistics for analysis_tools. 

456 

457 Version for "verifyCalib" style results, which have no exposure 

458 dimension. 

459 """ 

460 ConfigClass = CpVerifyRepackNoExpConfig 

461 _DefaultName = "cpVerifyRepack" 

462 

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

464 inputs = butlerQC.get(inputRefs) 

465 

466 inputs["detectorDims"] = [dict(exp.dataId.required) for exp in inputRefs.detectorStats] 

467 inputs["exposureDims"] = [] 

468 inputs["exposureStats"] = [] 

469 

470 outputs = self.run(**inputs) 

471 butlerQC.put(outputs, outputRefs) 

472 

473 

474class CpVerifyRepackPtcTask(CpVerifyRepackNoExpTask): 

475 stageName = "ptc" 

476 

477 def repackDetStats(self, detectorStats, detectorDims): 

478 rowList = [] 

479 

480 for detStats, detDims in zip(detectorStats, detectorDims): 

481 row = {} 

482 

483 instrument = detDims["instrument"] 

484 detector = detDims["detector"] 

485 

486 # Get amp stats 

487 for ampName, stats in detStats["AMP"].items(): 

488 row[ampName] = { 

489 "instrument": instrument, 

490 "detector": detector, 

491 "amplifier": ampName, 

492 "ampGain": stats["AMP_GAIN"], 

493 "ampNoise": stats["AMP_NOISE"], 

494 "ptcGain": stats["PTC_GAIN"], 

495 "ptcNoise": stats["PTC_NOISE"], 

496 "ptcTurnoff": stats["PTC_TURNOFF"], 

497 "ptcFitType": stats["PTC_FIT_TYPE"], 

498 "ptcBfeA00": stats["PTC_BFE_A00"], 

499 "ptcRowMeanVariance": stats["PTC_ROW_MEAN_VARIANCE"], 

500 "ptcRowMeanVarianceSlope": stats["PTC_ROW_MEAN_VARIANCE_SLOPE"], 

501 "ptcMaxRawMeans": stats["PTC_MAX_RAW_MEANS"], 

502 "ptcRawMeans": stats["PTC_RAW_MEANS"], 

503 "ptcExpIdmask": stats["PTC_EXP_ID_MASK"], 

504 "ptcCov10": stats["PTC_COV_10"], 

505 "ptcCov10FitSlope": stats["PTC_COV_10_FIT_SLOPE"], 

506 "ptcCov10FitOffset": stats["PTC_COV_10_FIT_OFFSET"], 

507 "ptcCov10FitSuccess": stats["PTC_COV_10_FIT_SUCCESS"], 

508 "ptcCov01": stats["PTC_COV_01"], 

509 "ptcCov01FitSlope": stats["PTC_COV_01_FIT_SLOPE"], 

510 "ptcCov01FitOffset": stats["PTC_COV_01_FIT_OFFSET"], 

511 "ptcCov01FitSuccess": stats["PTC_COV_01_FIT_SUCCESS"], 

512 "ptcCov11": stats["PTC_COV_11"], 

513 "ptcCov11FitSlope": stats["PTC_COV_11_FIT_SLOPE"], 

514 "ptcCov11FitOffset": stats["PTC_COV_11_FIT_OFFSET"], 

515 "ptcCov11FitSuccess": stats["PTC_COV_11_FIT_SUCCESS"], 

516 "ptcCov20": stats["PTC_COV_20"], 

517 "ptcCov20FitSlope": stats["PTC_COV_20_FIT_SLOPE"], 

518 "ptcCov20FitOffset": stats["PTC_COV_20_FIT_OFFSET"], 

519 "ptcCov20FitSuccess": stats["PTC_COV_20_FIT_SUCCESS"], 

520 "ptcCov02": stats["PTC_COV_02"], 

521 "ptcCov02FitSlope": stats["PTC_COV_02_FIT_SLOPE"], 

522 "ptcCov02FitOffset": stats["PTC_COV_02_FIT_OFFSET"], 

523 "ptcCov02FitSuccess": stats["PTC_COV_02_FIT_SUCCESS"], 

524 } 

525 # Get catalog stats 

526 # Get detector stats 

527 # Get metadata stats 

528 # Get verify stats 

529 for ampName, stats in detStats["VERIFY"]["AMP"].items(): 

530 row[ampName]["ptcVerifyGain"] = stats["PTC_GAIN"] 

531 row[ampName]["ptcVerifyNoise"] = stats["PTC_NOISE"] 

532 row[ampName]["ptcVerifyTurnoff"] = stats["PTC_TURNOFF"] 

533 row[ampName]["ptcVerifyBfeA00"] = stats["PTC_BFE_A00"] 

534 

535 # Get isr stats 

536 

537 # Append to output 

538 for ampName, stats in row.items(): 

539 rowList.append(stats) 

540 

541 return rowList 

542 

543 

544class CpVerifyRepackLinearityTask(CpVerifyRepackTask): 

545 stageName = "linearity" 

546 

547 def repackDetStats(self, detectorStats, detectorDims): 

548 rowList = [] 

549 

550 for detStats, detDims in zip(detectorStats, detectorDims): 

551 row = {} 

552 

553 instrument = detDims["instrument"] 

554 detector = detDims["detector"] 

555 

556 # Get amp stats 

557 for ampName, stats in detStats["AMP"].items(): 

558 centers, values = np.split(stats["LINEARITY_COEFFS"], 2) 

559 row[ampName] = { 

560 "instrument": instrument, 

561 "detector": detector, 

562 "amplifier": ampName, 

563 "fitParams": stats["FIT_PARAMS"], 

564 "fitParamsErr": stats["FIT_PARAMS_ERR"], 

565 "fitResiduals": stats["FIT_RESIDUALS"], 

566 "splineCenters": centers, 

567 "splineValues": values, 

568 "linearityType": stats["LINEARITY_TYPE"], 

569 "linearFit": stats["LINEAR_FIT"], 

570 } 

571 # Get catalog stats 

572 # Get detector stats 

573 # Get metadata stats 

574 # Get verify stats; no need to loop here. 

575 stats = detStats["VERIFY"] 

576 row["detector"] = { 

577 "instrument": instrument, 

578 "detector": detector, 

579 "linearityMaxResidualError": stats["MAX_RESIDUAL_ERROR"], 

580 } 

581 # Get isr stats 

582 

583 # Append to output 

584 for ampName, stats in row.items(): 

585 rowList.append(stats) 

586 

587 return rowList 

588 

589 

590class CpVerifyRepackCrosstalkTask(CpVerifyRepackTask): 

591 pass