Coverage for python/lsst/cp/verify/repackStats.py: 22%
211 statements
« prev ^ index » next coverage.py v7.4.1, created at 2024-02-06 12:54 +0000
« prev ^ index » next coverage.py v7.4.1, created at 2024-02-06 12:54 +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
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 )
80class CpVerifyRepackInstrumentConfig(pipeBase.PipelineTaskConfig,
81 pipelineConnections=CpVerifyRepackInstrumentConnections):
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 )
90class CpVerifyRepackTask(pipeBase.PipelineTask):
91 """Repack cpVerify statistics for analysis_tools.
93 This version is the base for calibrations with summary
94 dimensions of instrument only.
95 """
96 ConfigClass = CpVerifyRepackInstrumentConfig
97 _DefaultName = "cpVerifyRepack"
99 def runQuantum(self, butlerQC, inputRefs, outputRefs):
100 inputs = butlerQC.get(inputRefs)
102 inputs["detectorDims"] = [dict(exp.dataId.required) for exp in inputRefs.detectorStats]
103 inputs["exposureDims"] = [dict(exp.dataId.required) for exp in inputRefs.exposureStats]
105 outputs = self.run(**inputs)
106 butlerQC.put(outputs, outputRefs)
108 def run(self, detectorStats, detectorDims, exposureStats, exposureDims, runStats):
109 """
110 """
111 rowList = self.repack(detectorStats, detectorDims, exposureStats, exposureDims, runStats)
112 catalog = Table(rowList)
114 return pipeBase.Struct(
115 outputCatalog=catalog,
116 )
118 def repackDetStats(self, detectorStats, detectorDims):
119 raise NotImplementedError("Repack needs to be defined by subclasses.")
121 def repackExpStats(self, exposureStats, exposureDims):
122 # for expStats, expDims in zip(exposureStats, exposureDims):
123 raise NotImplementedError("Repack needs to be defined by subclasses.")
125 def repackRunStats(self, runStats):
126 # for runStats in runStats:
127 raise NotImplementedError("Repack needs to be defined by subclasses.")
129 def repack(self, detectorStats, detectorDims, exposureStats, exposureDims, runStats):
130 return self.repackDetStats(detectorStats, detectorDims)
133class CpVerifyRepackBiasTask(CpVerifyRepackTask):
134 stageName = "bias"
136 def repackDetStats(self, detectorStats, detectorDims):
137 rowList = []
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"]
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
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"]
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]
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])
185 # Create output table:
186 for ampName, stats in row.items():
187 rowList.append(stats)
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.
193 maxSerialLen = 0
194 maxParallelLen = 0
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"])
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 )
216 return rowList
219class CpVerifyRepackDarkTask(CpVerifyRepackTask):
220 stageName = "dark"
222 def repackDetStats(self, detectorStats, detectorDims):
223 rowList = []
225 for detStats, detDims in zip(detectorStats, detectorDims):
226 row = {}
227 instrument = detDims["instrument"]
228 exposure = detDims["exposure"]
229 detector = detDims["detector"]
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
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]
261 # Append to output
262 for ampName, stats in row.items():
263 rowList.append(stats)
265 return rowList
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 )
296 outputCatalog = cT.Output(
297 name="cpvCatalog",
298 doc="Output merged catalog.",
299 storageClass="ArrowAstropy",
300 dimensions={"instrument", "physical_filter"},
301 )
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 )
313class CpVerifyRepackFlatTask(CpVerifyRepackTask):
314 ConfigClass = CpVerifyRepackPhysicalFilterConfig
316 stageName = "flat"
318 def repackDetStats(self, detectorStats, detectorDims):
319 rowList = []
321 for detStats, detDims in zip(detectorStats, detectorDims):
322 row = {}
323 instrument = detDims["instrument"]
324 exposure = detDims["exposure"]
325 detector = detDims["detector"]
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
340 # Get metadata stats
341 # METADATA (RESIDUAL STDEV) {ampName} value
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 }
360 # Append to output
361 for ampName, stats in row.items():
362 rowList.append(stats)
364 return rowList
367class CpVerifyRepackDefectTask(CpVerifyRepackTask):
368 stageName = "defects"
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"]
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)
405 return rowList
408class CpVerifyRepackCtiTask(CpVerifyRepackTask):
409 stageName = "cti"
410 pass
413class CpVerifyRepackBfkTask(CpVerifyRepackTask):
414 stageName = "bfk"
415 pass
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 )
436 outputCatalog = cT.Output(
437 name="cpvCatalog",
438 doc="Output merged catalog.",
439 storageClass="ArrowAstropy",
440 dimensions={"instrument"},
441 )
444class CpVerifyRepackNoExpConfig(pipeBase.PipelineTaskConfig,
445 pipelineConnections=CpVerifyRepackNoExpConnections):
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 )
454class CpVerifyRepackNoExpTask(CpVerifyRepackTask):
455 """Repack cpVerify statistics for analysis_tools.
457 Version for "verifyCalib" style results, which have no exposure
458 dimension.
459 """
460 ConfigClass = CpVerifyRepackNoExpConfig
461 _DefaultName = "cpVerifyRepack"
463 def runQuantum(self, butlerQC, inputRefs, outputRefs):
464 inputs = butlerQC.get(inputRefs)
466 inputs["detectorDims"] = [dict(exp.dataId.required) for exp in inputRefs.detectorStats]
467 inputs["exposureDims"] = []
468 inputs["exposureStats"] = []
470 outputs = self.run(**inputs)
471 butlerQC.put(outputs, outputRefs)
474class CpVerifyRepackPtcTask(CpVerifyRepackNoExpTask):
475 stageName = "ptc"
477 def repackDetStats(self, detectorStats, detectorDims):
478 rowList = []
480 for detStats, detDims in zip(detectorStats, detectorDims):
481 row = {}
483 instrument = detDims["instrument"]
484 detector = detDims["detector"]
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"]
535 # Get isr stats
537 # Append to output
538 for ampName, stats in row.items():
539 rowList.append(stats)
541 return rowList
544class CpVerifyRepackLinearityTask(CpVerifyRepackTask):
545 stageName = "linearity"
547 def repackDetStats(self, detectorStats, detectorDims):
548 rowList = []
550 for detStats, detDims in zip(detectorStats, detectorDims):
551 row = {}
553 instrument = detDims["instrument"]
554 detector = detDims["detector"]
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
583 # Append to output
584 for ampName, stats in row.items():
585 rowList.append(stats)
587 return rowList
590class CpVerifyRepackCrosstalkTask(CpVerifyRepackTask):
591 pass