Coverage for python/lsst/cp/verify/mergeResults.py: 27%
155 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-06 05:32 -0700
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-06 05:32 -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 lsst.pipe.base as pipeBase
22import lsst.pipe.base.connectionTypes as cT
23import lsst.pex.config as pexConfig
26__all__ = ['CpVerifyExpMergeConfig', 'CpVerifyExpMergeTask',
27 'CpVerifyRunMergeConfig', 'CpVerifyRunMergeTask',
28 'CpVerifyVisitExpMergeConfig', 'CpVerifyVisitExpMergeTask',
29 'CpVerifyVisitRunMergeConfig', 'CpVerifyVisitRunMergeTask',
30 'CpVerifyCalibMergeConfig', 'CpVerifyCalibMergeTask']
33class CpVerifyExpMergeConnections(pipeBase.PipelineTaskConnections,
34 dimensions={"instrument", "exposure"},
35 defaultTemplates={}):
36 inputStats = cT.Input(
37 name="detectorStats",
38 doc="Input statistics to merge.",
39 storageClass="StructuredDataDict",
40 dimensions=["instrument", "exposure", "detector"],
41 multiple=True,
42 )
43 camera = cT.PrerequisiteInput(
44 name="camera",
45 storageClass="Camera",
46 doc="Input camera.",
47 dimensions=["instrument", ],
48 isCalibration=True,
49 )
51 outputStats = cT.Output(
52 name="exposureStats",
53 doc="Output statistics.",
54 storageClass="StructuredDataDict",
55 dimensions=["instrument", "exposure"],
56 )
59class CpVerifyExpMergeConfig(pipeBase.PipelineTaskConfig,
60 pipelineConnections=CpVerifyExpMergeConnections):
61 """Configuration parameters for exposure stats merging.
62 """
63 exposureStatKeywords = pexConfig.DictField(
64 keytype=str,
65 itemtype=str,
66 doc="Dictionary of statistics to run on the set of detector values. The key should be the test "
67 "name to record in the output, and the value should be the `lsst.afw.math` statistic name string.",
68 default={},
69 )
72class CpVerifyExpMergeTask(pipeBase.PipelineTask):
73 """Merge statistics from detectors together.
74 """
75 ConfigClass = CpVerifyExpMergeConfig
76 _DefaultName = 'cpVerifyExpMerge'
78 def runQuantum(self, butlerQC, inputRefs, outputRefs):
79 inputs = butlerQC.get(inputRefs)
81 dimensions = [dict(exp.dataId.required) for exp in inputRefs.inputStats]
82 inputs['inputDims'] = dimensions
84 outputs = self.run(**inputs)
85 butlerQC.put(outputs, outputRefs)
87 def run(self, inputStats, camera, inputDims):
88 """Merge statistics.
90 Parameters
91 ----------
92 inputStats : `list` [`dict`]
93 Measured statistics for a detector (from
94 CpVerifyStatsTask).
95 camera : `lsst.afw.cameraGeom.Camera`
96 The camera geometry for this exposure.
97 inputDims : `list` [`dict`]
98 List of dictionaries of input data dimensions/values.
99 Each list entry should contain:
101 ``"exposure"``
102 exposure id value (`int`)
103 ``"detector"``
104 detector id value (`int`)
106 Returns
107 -------
108 outputStats : `dict`
109 Merged full exposure statistics.
111 See Also
112 --------
113 lsst.cp.verify.CpVerifyStatsTask
115 Notes
116 -----
117 The outputStats should have a yaml representation of the form:
119 DET:
120 DetName1:
121 FAILURES:
122 - TEST_NAME
123 STAT: value
124 STAT2: value2
125 DetName2:
126 VERIFY:
127 TEST: boolean
128 TEST2: boolean
129 SUCCESS: boolean
130 """
131 outputStats = {}
132 success = True
134 mergedStats = {}
135 for detStats, dimensions in zip(inputStats, inputDims):
136 detId = dimensions['detector']
137 detName = camera[detId].getName()
138 calcStats = {}
140 mergedStats[detName] = detStats
142 if detStats['SUCCESS'] is True:
143 calcStats['SUCCESS'] = True
144 else:
145 calcStats['SUCCESS'] = False
146 calcStats['FAILURES'] = list()
147 success = False
148 # See if the detector failed
149 if 'DET' in detStats['VERIFY']:
150 detSuccess = detStats['VERIFY']['DET'].pop('SUCCESS', False)
151 if not detSuccess:
152 for testName, testResult in detStats['VERIFY']['DET'].items():
153 if testResult is False:
154 calcStats['FAILURES'].append(testName)
155 # See if the catalog failed
156 if 'CATALOG' in detStats['VERIFY']:
157 for testName, testResult in detStats['VERIFY']['CATALOG'].items():
158 if testResult is False:
159 calcStats['FAILURES'].append(testName)
160 # See if an amplifier failed
161 for ampName, ampStats in detStats['VERIFY']['AMP'].items():
162 ampSuccess = ampStats.pop('SUCCESS')
163 if not ampSuccess:
164 for testName, testResult in ampStats.items():
165 if testResult is False:
166 calcStats['FAILURES'].append(ampName + " " + testName)
168 outputStats[detName] = calcStats
170 exposureSuccess = True
171 if len(self.config.exposureStatKeywords):
172 outputStats['EXP'] = self.exposureStatistics(mergedStats)
173 outputStats['VERIFY'], exposureSuccess = self.verify(mergedStats, outputStats)
175 outputStats['SUCCESS'] = success & exposureSuccess
177 return pipeBase.Struct(
178 outputStats=outputStats,
179 )
181 def exposureStatistics(self, statisticsDict):
182 """Calculate exposure level statistics based on the existing
183 per-amplifier and per-detector measurements.
185 Parameters
186 ----------
187 statisticsDictionary : `dict [`str`, `dict` [`str`, scalar]],
188 Dictionary of measured statistics. The top level
189 dictionary is keyed on the detector names, and contains
190 the measured statistics from the per-detector
191 measurements.
193 Returns
194 -------
195 outputStatistics : `dict` [`str, scalar]
196 A dictionary of the statistics measured and their values.
197 """
198 raise NotImplementedError("Subclasses must implement verification criteria.")
200 def verify(self, detectorStatistics, statisticsDictionary):
202 """Verify if the measured statistics meet the verification criteria.
204 Parameters
205 ----------
206 detectorStatistics : `dict` [`str`, `dict` [`str`, scalar]]
207 Merged set of input detector level statistics.
208 statisticsDictionary : `dict` [`str`, `dict` [`str`, scalar]]
209 Dictionary of measured statistics. The inner dictionary
210 should have keys that are statistic names (`str`) with
211 values that are some sort of scalar (`int` or `float` are
212 the mostly likely types).
214 Returns
215 -------
216 outputStatistics : `dict` [`str`, `dict` [`str`, `bool`]]
217 A dictionary indexed by the amplifier name, containing
218 dictionaries of the verification criteria.
219 success : `bool`
220 A boolean indicating if all tests have passed.
222 Raises
223 ------
224 NotImplementedError :
225 This method must be implemented by the calibration-type
226 subclass.
227 """
228 raise NotImplementedError("Subclasses must implement verification criteria.")
231class CpVerifyRunMergeConnections(pipeBase.PipelineTaskConnections,
232 dimensions={"instrument"},
233 defaultTemplates={}):
234 inputStats = cT.Input(
235 name="exposureStats",
236 doc="Input statistics to merge.",
237 storageClass="StructuredDataDict",
238 dimensions=["instrument", "exposure"],
239 multiple=True,
240 )
242 outputStats = cT.Output(
243 name="runStats",
244 doc="Output statistics.",
245 storageClass="StructuredDataDict",
246 dimensions=["instrument"],
247 )
250class CpVerifyRunMergeConfig(pipeBase.PipelineTaskConfig,
251 pipelineConnections=CpVerifyRunMergeConnections):
252 """Configuration paramters for exposure stats merging.
253 """
254 runStatKeywords = pexConfig.DictField(
255 keytype=str,
256 itemtype=str,
257 doc="Dictionary of statistics to run on the set of exposure values. The key should be the test "
258 "name to record in the output, and the value should be the `lsst.afw.math` statistic name string.",
259 default={},
260 )
263class CpVerifyRunMergeTask(pipeBase.PipelineTask):
264 """Merge statistics from detectors together.
265 """
266 ConfigClass = CpVerifyRunMergeConfig
267 _DefaultName = 'cpVerifyRunMerge'
269 def runQuantum(self, butlerQC, inputRefs, outputRefs):
270 inputs = butlerQC.get(inputRefs)
272 dimensions = [dict(exp.dataId.required) for exp in inputRefs.inputStats]
273 inputs['inputDims'] = dimensions
275 outputs = self.run(**inputs)
276 butlerQC.put(outputs, outputRefs)
278 def run(self, inputStats, inputDims):
279 """Merge statistics.
281 Parameters
282 ----------
283 inputStats : `list` [`dict`]
284 Measured statistics for a detector.
285 inputDims : `list` [`dict`]
286 List of dictionaries of input data dimensions/values.
287 Each list entry should contain:
289 ``"exposure"``
290 exposure id value (`int`)
292 Returns
293 -------
294 outputStats : `dict`
295 Merged full exposure statistics.
297 Notes
298 -----
299 The outputStats should have a yaml representation as follows.
301 VERIFY:
302 ExposureId1:
303 VERIFY_TEST1: boolean
304 VERIFY_TEST2: boolean
305 ExposureId2:
306 [...]
307 TEST_VALUE: boolean
308 TEST_VALUE2: boolean
309 """
310 outputStats = {}
311 success = True
312 for expStats, dimensions in zip(inputStats, inputDims):
313 expId = dimensions.get('exposure', dimensions.get('visit', None))
314 if expId is None:
315 raise RuntimeError("Could not identify the exposure from %s", dimensions)
317 calcStats = {}
319 expSuccess = expStats.pop('SUCCESS')
320 if expSuccess:
321 calcStats['SUCCESS'] = True
322 else:
323 calcStats['FAILURES'] = list()
324 success = False
325 for detName, detStats in expStats.items():
326 detSuccess = detStats.pop('SUCCESS')
327 if not detSuccess:
328 for testName in expStats[detName]['FAILURES']:
329 calcStats['FAILURES'].append(detName + " " + testName)
331 outputStats[expId] = calcStats
333 runSuccess = True
334 if len(self.config.runStatKeywords):
335 outputStats['VERIFY'], runSuccess = self.verify(outputStats)
337 outputStats['SUCCESS'] = success & runSuccess
339 return pipeBase.Struct(
340 outputStats=outputStats,
341 )
343 def verify(self, statisticsDictionary):
344 """Verify if the measured statistics meet the verification criteria.
346 Parameters
347 ----------
348 statisticsDictionary : `dict` [`str`, `dict`],
349 Dictionary of measured statistics. The inner dictionary
350 should have keys that are statistic names (`str`) with
351 values that are some sort of scalar (`int` or `float` are
352 the mostly likely types).
354 Returns
355 -------
356 outputStatistics : `dict` [`str`, `dict` [`str`, `bool`]]
357 A dictionary indexed by the amplifier name, containing
358 dictionaries of the verification criteria.
359 success : `bool`
360 A boolean indicating if all tests have passed.
362 Raises
363 ------
364 NotImplementedError :
365 This method must be implemented by the calibration-type
366 subclass.
368 """
369 raise NotImplementedError("Subclasses must implement verification criteria.")
372class CpVerifyVisitExpMergeConnections(pipeBase.PipelineTaskConnections,
373 dimensions={"instrument", "visit"},
374 defaultTemplates={}):
375 inputStats = cT.Input(
376 name="detectorStats",
377 doc="Input statistics to merge.",
378 storageClass="StructuredDataDict",
379 dimensions=["instrument", "visit", "detector"],
380 multiple=True,
381 )
382 camera = cT.PrerequisiteInput(
383 name="camera",
384 storageClass="Camera",
385 doc="Input camera.",
386 dimensions=["instrument", ],
387 isCalibration=True,
388 )
390 outputStats = cT.Output(
391 name="exposureStats",
392 doc="Output statistics.",
393 storageClass="StructuredDataDict",
394 dimensions=["instrument", "visit"],
395 )
398class CpVerifyVisitExpMergeConfig(CpVerifyExpMergeConfig,
399 pipelineConnections=CpVerifyVisitExpMergeConnections):
400 pass
403class CpVerifyVisitExpMergeTask(CpVerifyExpMergeTask):
404 """Merge visit based data."""
406 ConfigClass = CpVerifyVisitExpMergeConfig
407 _DefaultName = 'cpVerifyVisitExpMerge'
409 pass
412class CpVerifyVisitRunMergeConnections(pipeBase.PipelineTaskConnections,
413 dimensions={"instrument"},
414 defaultTemplates={}):
415 inputStats = cT.Input(
416 name="exposureStats",
417 doc="Input statistics to merge.",
418 storageClass="StructuredDataDict",
419 dimensions=["instrument", "visit"],
420 multiple=True,
421 )
423 outputStats = cT.Output(
424 name="runStats",
425 doc="Output statistics.",
426 storageClass="StructuredDataDict",
427 dimensions=["instrument"],
428 )
431class CpVerifyVisitRunMergeConfig(CpVerifyRunMergeConfig,
432 pipelineConnections=CpVerifyVisitRunMergeConnections):
433 pass
436class CpVerifyVisitRunMergeTask(CpVerifyRunMergeTask):
437 """Merge visit based data."""
439 ConfigClass = CpVerifyVisitRunMergeConfig
440 _DefaultName = 'cpVerifyVisitRunMerge'
442 pass
445class CpVerifyCalibMergeConnections(pipeBase.PipelineTaskConnections,
446 dimensions={"instrument"},
447 defaultTemplates={}):
448 inputStats = cT.Input(
449 name="exposureStats",
450 doc="Input statistics to merge.",
451 storageClass="StructuredDataDict",
452 dimensions=["instrument", "detector"],
453 multiple=True,
454 )
456 outputStats = cT.Output(
457 name="exposureStats",
458 doc="Output statistics.",
459 storageClass="StructuredDataDict",
460 dimensions=["instrument"],
461 )
464class CpVerifyCalibMergeConfig(pipeBase.PipelineTaskConfig,
465 pipelineConnections=CpVerifyCalibMergeConnections):
466 """Configuration paramters for exposure stats merging.
467 """
468 runStatKeywords = pexConfig.DictField(
469 keytype=str,
470 itemtype=str,
471 doc="Dictionary of statistics to run on the set of exposure values. The key should be the test "
472 "name to record in the output, and the value should be the `lsst.afw.math` statistic name string.",
473 default={},
474 )
477class CpVerifyCalibMergeTask(pipeBase.PipelineTask):
478 """Merge statistics from detectors together.
479 """
480 ConfigClass = CpVerifyCalibMergeConfig
481 _DefaultName = 'cpVerifyCalibMerge'
483 def runQuantum(self, butlerQC, inputRefs, outputRefs):
484 inputs = butlerQC.get(inputRefs)
486 dimensions = [dict(exp.dataId.required) for exp in inputRefs.inputStats]
487 inputs['inputDims'] = dimensions
489 outputs = self.run(**inputs)
490 butlerQC.put(outputs, outputRefs)
492 def run(self, inputStats, inputDims):
493 """Merge statistics.
495 Parameters
496 ----------
497 inputStats : `list` [`dict`]
498 Measured statistics for a detector.
499 inputDims : `list` [`dict`]
500 List of dictionaries of input data dimensions/values.
501 Each list entry should contain:
503 ``"detector"``
504 detector id value (`int`)
506 Returns
507 -------
508 outputStats : `dict`
509 Merged full exposure statistics.
511 Notes
512 -----
513 The outputStats should have a yaml representation as follows.
515 Detector detId:
516 FAILURES:
517 - Detector detId TEST_NAME
518 SUCCESS: boolean
519 """
520 outputStats = {}
521 success = True
522 for detStats, dimensions in zip(inputStats, inputDims):
523 detId = dimensions['detector']
524 detName = f"Detector {detId}"
525 calcStats = {}
527 detSuccess = detStats.pop('SUCCESS')
528 if detSuccess:
529 calcStats['SUCCESS'] = True
530 else:
531 calcStats['FAILURES'] = list()
532 success = False
533 for testName in detStats['VERIFY']:
534 calcStats['FAILURES'].append(detName + " " + testName)
536 outputStats[detName] = calcStats
538 runSuccess = True
539 if len(self.config.runStatKeywords):
540 outputStats['VERIFY'], runSuccess = self.verify(outputStats)
542 outputStats['SUCCESS'] = success & runSuccess
544 return pipeBase.Struct(
545 outputStats=outputStats,
546 )
548 def verify(self, statisticsDictionary):
549 """Verify if the measured statistics meet the verification criteria.
551 Parameters
552 ----------
553 statisticsDictionary : `dict` [`str`, `dict`],
554 Dictionary of measured statistics. The inner dictionary
555 should have keys that are statistic names (`str`) with
556 values that are some sort of scalar (`int` or `float` are
557 the mostly likely types).
559 Returns
560 -------
561 outputStatistics : `dict` [`str`, `dict` [`str`, `bool`]]
562 A dictionary indexed by the amplifier name, containing
563 dictionaries of the verification criteria.
564 success : `bool`
565 A boolean indicating if all tests have passed.
567 Raises
568 ------
569 NotImplementedError :
570 This method must be implemented by the calibration-type
571 subclass.
573 """
574 raise NotImplementedError("Subclasses must implement verification criteria.")