Coverage for python/lsst/cp/verify/repackStats.py: 22%
227 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-16 04:27 -0700
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-16 04:27 -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 numpy as np
23from astropy.table import Table
25import lsst.pipe.base as pipeBase
26import lsst.pipe.base.connectionTypes as cT
27import lsst.pex.config as pexConfig
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]
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 )
72 outputCatalog = cT.Output(
73 name="cpvCatalog",
74 doc="Output merged catalog.",
75 storageClass="ArrowAstropy",
76 dimensions={"instrument", },
77 )
78 matrixCatalog = cT.Output(
79 name="cpvMatrix",
80 doc="Output matrix catalog.",
81 storageClass="ArrowAstropy",
82 dimensions={"instrument", },
83 )
85 def __init__(self, *, config=None):
86 super().__init__(config=config)
88 if not config.hasMatrixCatalog:
89 self.outputs.remove("matrixCatalog")
92class CpVerifyRepackInstrumentConfig(pipeBase.PipelineTaskConfig,
93 pipelineConnections=CpVerifyRepackInstrumentConnections):
95 expectedDistributionLevels = pexConfig.ListField(
96 dtype=float,
97 doc="Percentile levels expected in the calibration header.",
98 default=[0, 5, 16, 50, 84, 95, 100],
99 )
100 hasMatrixCatalog = pexConfig.Field(
101 dtype=bool,
102 doc="Will a matrix catalog be created?",
103 default=False,
104 )
107class CpVerifyRepackTask(pipeBase.PipelineTask):
108 """Repack cpVerify statistics for analysis_tools.
110 This version is the base for calibrations with summary
111 dimensions of instrument only.
112 """
113 ConfigClass = CpVerifyRepackInstrumentConfig
114 _DefaultName = "cpVerifyRepack"
116 def runQuantum(self, butlerQC, inputRefs, outputRefs):
117 inputs = butlerQC.get(inputRefs)
119 inputs["detectorDims"] = [dict(exp.dataId.required) for exp in inputRefs.detectorStats]
120 inputs["exposureDims"] = [dict(exp.dataId.required) for exp in inputRefs.exposureStats]
122 outputs = self.run(**inputs)
123 butlerQC.put(outputs, outputRefs)
125 def run(self, detectorStats, detectorDims, exposureStats, exposureDims, runStats):
126 """
127 """
128 results = self.repack(detectorStats, detectorDims, exposureStats, exposureDims, runStats)
129 catalog = Table(results.rowList)
131 matrixCatalog = None
132 if self.config.hasMatrixCatalog:
133 matrixCatalog = Table(results.matrixList)
134 return pipeBase.Struct(
135 outputCatalog=catalog,
136 matrixCatalog=matrixCatalog,
137 )
139 def repackDetStats(self, detectorStats, detectorDims):
140 raise NotImplementedError("Repack needs to be defined by subclasses.")
142 def repackExpStats(self, exposureStats, exposureDims):
143 # for expStats, expDims in zip(exposureStats, exposureDims):
144 raise NotImplementedError("Repack needs to be defined by subclasses.")
146 def repackRunStats(self, runStats):
147 # for runStats in runStats:
148 raise NotImplementedError("Repack needs to be defined by subclasses.")
150 def repack(self, detectorStats, detectorDims, exposureStats, exposureDims, runStats):
151 return self.repackDetStats(detectorStats, detectorDims)
154class CpVerifyRepackBiasTask(CpVerifyRepackTask):
155 stageName = "bias"
157 def repackDetStats(self, detectorStats, detectorDims):
158 rowList = []
160 for detStats, detDims in zip(detectorStats, detectorDims):
161 row = {}
162 instrument = detDims["instrument"]
163 exposure = detDims["exposure"]
164 detector = detDims["detector"]
165 mjd = detStats["ISR"]["MJD"]
167 # Get amp stats
168 # AMP {ampName} [CR_NOISE MEAN NOISE] value
169 for ampName, stats in detStats["AMP"].items():
170 row[ampName] = {
171 "instrument": instrument,
172 "exposure": exposure,
173 "mjd": mjd,
174 "detector": detector,
175 "amplifier": ampName,
176 "biasMean": stats["MEAN"],
177 "biasNoise": stats["NOISE"],
178 "biasCrNoise": stats["CR_NOISE"]
179 }
180 # Get catalog stats CATALOG
181 # Get detector stats DET
182 # Get metadata stats
183 # METADATA (RESIDUAL STDEV) {ampName} value
184 for ampName, value in detStats["METADATA"]["RESIDUAL STDEV"].items():
185 row[ampName]["biasReadNoise"] = value
187 # Get verify stats
188 for ampName, stats in detStats["VERIFY"]["AMP"].items():
189 row[ampName]["biasVerifyMean"] = stats["MEAN"]
190 row[ampName]["biasVerifyNoise"] = stats["NOISE"]
191 row[ampName]["biasVerifyCrNoise"] = stats["CR_NOISE"]
192 row[ampName]["biasVerifyReadNoiseConsistent"] = stats["READ_NOISE_CONSISTENT"]
194 # Get isr stats
195 for ampName, stats in detStats["ISR"]["CALIBDIST"].items():
196 for level in self.config.expectedDistributionLevels:
197 key = f"LSST CALIB {self.stageName.upper()} {ampName} DISTRIBUTION {level}-PCT"
198 row[ampName][f"biasDistribution_{level}"] = stats[key]
200 projStats = detStats["ISR"]["PROJECTION"]
201 for ampName in projStats["AMP_HPROJECTION"].keys():
202 row[ampName]["biasSerialProfile"] = np.array(projStats["AMP_HPROJECTION"][ampName])
203 for ampName in projStats["AMP_VPROJECTION"].keys():
204 row[ampName]["biasParallelProfile"] = np.array(projStats["AMP_VPROJECTION"][ampName])
206 shiftStats = detStats["ISR"]["BIASSHIFT"]
207 for ampName, stats in shiftStats.items():
208 row[ampName]["biasShiftCount"] = len(stats["BIAS_SHIFTS"])
209 row[ampName]["biasShiftNoise"] = stats["LOCAL_NOISE"]
210 corrStats = detStats["ISR"]["AMPCORR"]
212 # Create output table:
213 for ampName, stats in row.items():
214 rowList.append(stats)
216 # We need all rows of biasParallelProfile and biasParallelProfile
217 # to be the same length for serialization. Therefore, we pad
218 # to the longest length.
220 maxSerialLen = 0
221 maxParallelLen = 0
223 for row in rowList:
224 if len(row["biasSerialProfile"]) > maxSerialLen:
225 maxSerialLen = len(row["biasSerialProfile"])
226 if len(row["biasParallelProfile"]) > maxParallelLen:
227 maxParallelLen = len(row["biasParallelProfile"])
229 for row in rowList:
230 if len(row["biasSerialProfile"]) < maxSerialLen:
231 row["biasSerialProfile"] = np.pad(
232 row["biasSerialProfile"],
233 (0, maxSerialLen - len(row["biasSerialProfile"])),
234 constant_values=np.nan,
235 )
236 if len(row["biasParallelProfile"]) < maxParallelLen:
237 row["biasParallelProfile"] = np.pad(
238 row["biasParallelProfile"],
239 (0, maxParallelLen - len(row["biasParallelProfile"])),
240 constant_values=np.nan,
241 )
243 return pipeBase.Struct(
244 rowList=rowList,
245 matrixList=corrStats,
246 )
249class CpVerifyRepackDarkTask(CpVerifyRepackTask):
250 stageName = "dark"
252 def repackDetStats(self, detectorStats, detectorDims):
253 rowList = []
255 for detStats, detDims in zip(detectorStats, detectorDims):
256 row = {}
257 instrument = detDims["instrument"]
258 exposure = detDims["exposure"]
259 detector = detDims["detector"]
261 # Get amp stats
262 # AMP {ampName} [CR_NOISE MEAN NOISE] value
263 for ampName, stats in detStats["AMP"].items():
264 row[ampName] = {
265 "instrument": instrument,
266 "exposure": exposure,
267 "detector": detector,
268 "amplifier": ampName,
269 "darkMean": stats["MEAN"],
270 "darkNoise": stats["NOISE"],
271 "darkCrNoise": stats["CR_NOISE"]
272 }
273 # Get catalog stats
274 # Get detector stats
275 # Get metadata stats
276 for ampName, value in detStats["METADATA"]["RESIDUAL STDEV"].items():
277 row[ampName]["darkReadNoise"] = value
279 # Get verify stats
280 for ampName, stats in detStats["VERIFY"]["AMP"].items():
281 row[ampName]["darkVerifyMean"] = stats["MEAN"]
282 row[ampName]["darkVerifyNoise"] = stats["NOISE"]
283 row[ampName]["darkVerifyCrNoise"] = stats["CR_NOISE"]
284 row[ampName]["darkVerifyReadNoiseConsistent"] = stats["READ_NOISE_CONSISTENT"]
285 # Get isr stats
286 for ampName, stats in detStats["ISR"]["CALIBDIST"].items():
287 for level in self.config.expectedDistributionLevels:
288 key = f"LSST CALIB {self.stageName.upper()} {ampName} DISTRIBUTION {level}-PCT"
289 row[ampName][f"darkDistribution_{level}"] = stats[key]
291 # Append to output
292 for ampName, stats in row.items():
293 rowList.append(stats)
295 return pipeBase.Struct(
296 rowList=rowList,
297 )
300class CpVerifyRepackPhysicalFilterConnections(pipeBase.PipelineTaskConnections,
301 dimensions={"instrument", "physical_filter"},
302 defaultTemplate={}):
303 """Connections class for calibration statistics with physical_filter
304 (and instrument) dimensions.
305 """
306 detectorStats = cT.Input(
307 name="detectorStats",
308 doc="Input detector statistics.",
309 storageClass="StructuredDataDict",
310 dimensions={"instrument", "exposure", "detector"},
311 multiple=True,
312 )
313 exposureStats = cT.Input(
314 name="exposureStats",
315 doc="Input exposure statistics.",
316 storageClass="StructuredDataDict",
317 dimensions={"instrument", "exposure"},
318 multiple=True,
319 )
320 runStats = cT.Input(
321 name="runStats",
322 doc="Input Run statistics.",
323 storageClass="StructuredDataDict",
324 dimensions={"instrument"},
325 multiple=True,
326 )
328 outputCatalog = cT.Output(
329 name="cpvCatalog",
330 doc="Output merged catalog.",
331 storageClass="ArrowAstropy",
332 dimensions={"instrument", "physical_filter"},
333 )
336class CpVerifyRepackPhysicalFilterConfig(pipeBase.PipelineTaskConfig,
337 pipelineConnections=CpVerifyRepackPhysicalFilterConnections):
338 expectedDistributionLevels = pexConfig.ListField(
339 dtype=float,
340 doc="Percentile levels expected in the calibration header.",
341 default=[0, 5, 16, 50, 84, 95, 100],
342 )
343 hasMatrixCatalog = pexConfig.Field(
344 dtype=bool,
345 doc="Will a matrix catalog be created?",
346 default=False,
347 )
350class CpVerifyRepackFlatTask(CpVerifyRepackTask):
351 ConfigClass = CpVerifyRepackPhysicalFilterConfig
353 stageName = "flat"
355 def repackDetStats(self, detectorStats, detectorDims):
356 rowList = []
358 for detStats, detDims in zip(detectorStats, detectorDims):
359 row = {}
360 instrument = detDims["instrument"]
361 exposure = detDims["exposure"]
362 detector = detDims["detector"]
364 # Get amp stats
365 # AMP {ampName} [MEAN NOISE] value
366 for ampName, stats in detStats["AMP"].items():
367 row[ampName] = {
368 "instrument": instrument,
369 "exposure": exposure,
370 "detector": detector,
371 "amplifier": ampName,
372 "flatMean": stats["MEAN"],
373 "flatNoise": stats["NOISE"],
374 }
375 # Get catalog stats CATALOG
377 # Get metadata stats
378 # METADATA (RESIDUAL STDEV) {ampName} value
380 # Get verify stats
381 for ampName, stats in detStats["VERIFY"]["AMP"].items():
382 row[ampName]["flatVerifyNoise"] = stats["NOISE"]
383 # Get isr stats
384 for ampName, stats in detStats["ISR"]["CALIBDIST"].items():
385 for level in self.config.expectedDistributionLevels:
386 key = f"LSST CALIB {self.stageName.upper()} {ampName} DISTRIBUTION {level}-PCT"
387 row[ampName][f"flatDistribution_{level}"] = stats[key]
388 # Get detector stats
389 # DET
390 row["detector"] = {"instrument": instrument,
391 "exposure": exposure,
392 "detector": detector,
393 "flatDetMean": detStats["DET"]["MEAN"],
394 "flatDetScatter": detStats["DET"]["SCATTER"],
395 }
397 # Append to output
398 for ampName, stats in row.items():
399 rowList.append(stats)
401 return pipeBase.Struct(
402 rowList=rowList,
403 )
406class CpVerifyRepackDefectTask(CpVerifyRepackTask):
407 stageName = "defects"
409 def repackDetStats(self, detectorStats, detectorDims):
410 rowList = []
411 for detStats, detDims in zip(detectorStats, detectorDims):
412 row = {}
413 instrument = detDims["instrument"]
414 detector = detDims["detector"]
416 # Get amp stats
417 for ampName, stats in detStats["AMP"].items():
418 row[ampName] = {
419 "instrument": instrument,
420 "detector": detector,
421 "amplifier": ampName,
422 }
423 # Get catalog stats CATALOG
424 # Get metadata stats METADATA
425 # Get verify stats VERIFY
426 # Get isr stats ISR
427 nBadColumns = np.nan
428 for ampName, stats in detStats["ISR"]["CALIBDIST"].items():
429 if ampName == "detector":
430 nBadColumns = stats[ampName]["LSST CALIB DEFECTS N_BAD_COLUMNS"]
431 else:
432 key = f"LSST CALIB DEFECTS {ampName} N_HOT"
433 row[ampName]["hotPixels"] = stats[ampName][key]
434 key = f"LSST CALIB DEFECTS {ampName} N_COLD"
435 row[ampName]["coldPixels"] = stats[ampName][key]
436 # Get detector stats DET
437 row["detector"] = {"instrument": instrument,
438 "detector": detector,
439 "nBadColumns": nBadColumns,
440 }
441 for ampName, stats in row.items():
442 rowList.append(stats)
444 return rowList
447class CpVerifyRepackCtiTask(CpVerifyRepackTask):
448 stageName = "cti"
449 pass
452class CpVerifyRepackBfkTask(CpVerifyRepackTask):
453 stageName = "bfk"
454 pass
457class CpVerifyRepackNoExpConnections(pipeBase.PipelineTaskConnections,
458 dimensions={"instrument"},
459 defaultTemplate={}):
460 detectorStats = cT.Input(
461 name="detectorStats",
462 doc="Input detector statistics.",
463 storageClass="StructuredDataDict",
464 dimensions={"instrument", "detector"},
465 multiple=True,
466 )
467 runStats = cT.Input(
468 name="runStats",
469 doc="Input Run statistics.",
470 storageClass="StructuredDataDict",
471 dimensions={"instrument"},
472 multiple=True,
473 )
475 outputCatalog = cT.Output(
476 name="cpvCatalog",
477 doc="Output merged catalog.",
478 storageClass="ArrowAstropy",
479 dimensions={"instrument"},
480 )
483class CpVerifyRepackNoExpConfig(pipeBase.PipelineTaskConfig,
484 pipelineConnections=CpVerifyRepackNoExpConnections):
486 expectedDistributionLevels = pexConfig.ListField(
487 dtype=float,
488 doc="Percentile levels expected in the calibration header.",
489 default=[0, 5, 16, 50, 84, 95, 100],
490 )
491 hasMatrixCatalog = pexConfig.Field(
492 dtype=bool,
493 doc="Will a matrix catalog be created?",
494 default=False,
495 )
498class CpVerifyRepackNoExpTask(CpVerifyRepackTask):
499 """Repack cpVerify statistics for analysis_tools.
501 Version for "verifyCalib" style results, which have no exposure
502 dimension.
503 """
504 ConfigClass = CpVerifyRepackNoExpConfig
505 _DefaultName = "cpVerifyRepack"
507 def runQuantum(self, butlerQC, inputRefs, outputRefs):
508 inputs = butlerQC.get(inputRefs)
510 inputs["detectorDims"] = [dict(exp.dataId.required) for exp in inputRefs.detectorStats]
511 inputs["exposureDims"] = []
512 inputs["exposureStats"] = []
514 outputs = self.run(**inputs)
515 butlerQC.put(outputs, outputRefs)
518class CpVerifyRepackPtcTask(CpVerifyRepackNoExpTask):
519 stageName = "ptc"
521 def repackDetStats(self, detectorStats, detectorDims):
522 rowList = []
524 for detStats, detDims in zip(detectorStats, detectorDims):
525 row = {}
527 instrument = detDims["instrument"]
528 detector = detDims["detector"]
530 # Get amp stats
531 for ampName, stats in detStats["AMP"].items():
532 row[ampName] = {
533 "instrument": instrument,
534 "detector": detector,
535 "amplifier": ampName,
536 "ampGain": stats["AMP_GAIN"],
537 "ampNoise": stats["AMP_NOISE"],
538 "ptcGain": stats["PTC_GAIN"],
539 "ptcNoise": stats["PTC_NOISE"],
540 "ptcTurnoff": stats["PTC_TURNOFF"],
541 "ptcFitType": stats["PTC_FIT_TYPE"],
542 "ptcBfeA00": stats["PTC_BFE_A00"],
543 "ptcRowMeanVariance": stats["PTC_ROW_MEAN_VARIANCE"],
544 "ptcRowMeanVarianceSlope": stats["PTC_ROW_MEAN_VARIANCE_SLOPE"],
545 "ptcMaxRawMeans": stats["PTC_MAX_RAW_MEANS"],
546 "ptcRawMeans": stats["PTC_RAW_MEANS"],
547 "ptcExpIdmask": stats["PTC_EXP_ID_MASK"],
548 "ptcCov10": stats["PTC_COV_10"],
549 "ptcCov10FitSlope": stats["PTC_COV_10_FIT_SLOPE"],
550 "ptcCov10FitOffset": stats["PTC_COV_10_FIT_OFFSET"],
551 "ptcCov10FitSuccess": stats["PTC_COV_10_FIT_SUCCESS"],
552 "ptcCov01": stats["PTC_COV_01"],
553 "ptcCov01FitSlope": stats["PTC_COV_01_FIT_SLOPE"],
554 "ptcCov01FitOffset": stats["PTC_COV_01_FIT_OFFSET"],
555 "ptcCov01FitSuccess": stats["PTC_COV_01_FIT_SUCCESS"],
556 "ptcCov11": stats["PTC_COV_11"],
557 "ptcCov11FitSlope": stats["PTC_COV_11_FIT_SLOPE"],
558 "ptcCov11FitOffset": stats["PTC_COV_11_FIT_OFFSET"],
559 "ptcCov11FitSuccess": stats["PTC_COV_11_FIT_SUCCESS"],
560 "ptcCov20": stats["PTC_COV_20"],
561 "ptcCov20FitSlope": stats["PTC_COV_20_FIT_SLOPE"],
562 "ptcCov20FitOffset": stats["PTC_COV_20_FIT_OFFSET"],
563 "ptcCov20FitSuccess": stats["PTC_COV_20_FIT_SUCCESS"],
564 "ptcCov02": stats["PTC_COV_02"],
565 "ptcCov02FitSlope": stats["PTC_COV_02_FIT_SLOPE"],
566 "ptcCov02FitOffset": stats["PTC_COV_02_FIT_OFFSET"],
567 "ptcCov02FitSuccess": stats["PTC_COV_02_FIT_SUCCESS"],
568 }
569 # Get catalog stats
570 # Get detector stats
571 # Get metadata stats
572 # Get verify stats
573 for ampName, stats in detStats["VERIFY"]["AMP"].items():
574 row[ampName]["ptcVerifyGain"] = stats["PTC_GAIN"]
575 row[ampName]["ptcVerifyNoise"] = stats["PTC_NOISE"]
576 row[ampName]["ptcVerifyTurnoff"] = stats["PTC_TURNOFF"]
577 row[ampName]["ptcVerifyBfeA00"] = stats["PTC_BFE_A00"]
579 # Get isr stats
581 # Append to output
582 for ampName, stats in row.items():
583 rowList.append(stats)
585 return pipeBase.Struct(
586 rowList=rowList,
587 )
590class CpVerifyRepackLinearityTask(CpVerifyRepackTask):
591 stageName = "linearity"
593 def repackDetStats(self, detectorStats, detectorDims):
594 rowList = []
596 for detStats, detDims in zip(detectorStats, detectorDims):
597 row = {}
599 instrument = detDims["instrument"]
600 detector = detDims["detector"]
602 # Get amp stats
603 for ampName, stats in detStats["AMP"].items():
604 centers, values = np.split(stats["LINEARITY_COEFFS"], 2)
605 row[ampName] = {
606 "instrument": instrument,
607 "detector": detector,
608 "amplifier": ampName,
609 "fitParams": stats["FIT_PARAMS"],
610 "fitParamsErr": stats["FIT_PARAMS_ERR"],
611 "fitResiduals": stats["FIT_RESIDUALS"],
612 "splineCenters": centers,
613 "splineValues": values,
614 "linearityType": stats["LINEARITY_TYPE"],
615 "linearFit": stats["LINEAR_FIT"],
616 }
617 # Get catalog stats
618 # Get detector stats
619 # Get metadata stats
620 # Get verify stats; no need to loop here.
621 stats = detStats["VERIFY"]
622 row["detector"] = {
623 "instrument": instrument,
624 "detector": detector,
625 "linearityMaxResidualError": stats["MAX_RESIDUAL_ERROR"],
626 }
627 # Get isr stats
629 # Append to output
630 for ampName, stats in row.items():
631 rowList.append(stats)
633 return pipeBase.Struct(
634 rowList=rowList,
635 )
638class CpVerifyRepackCrosstalkTask(CpVerifyRepackTask):
639 pass