Coverage for python/lsst/cp/verify/verifyBias.py: 20%
57 statements
« prev ^ index » next coverage.py v7.4.1, created at 2024-02-07 14:52 +0000
« prev ^ index » next coverage.py v7.4.1, created at 2024-02-07 14:52 +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
23import lsst.afw.math as afwMath
25from lsst.geom import Point2I, Extent2I, Box2I
26from lsst.pex.config import Field
27from .verifyStats import CpVerifyStatsConfig, CpVerifyStatsTask, CpVerifyStatsConnections
29__all__ = ['CpVerifyBiasConfig', 'CpVerifyBiasTask']
32class CpVerifyBiasConfig(CpVerifyStatsConfig,
33 pipelineConnections=CpVerifyStatsConnections):
34 """Inherits from base CpVerifyStatsConfig.
35 """
37 ampCornerBoxSize = Field(
38 dtype=int,
39 doc="Size of box to use for measure corner signal.",
40 default=200,
41 )
43 def setDefaults(self):
44 super().setDefaults()
45 self.imageStatKeywords = {'MEAN': 'MEAN', # noqa F841
46 'NOISE': 'STDEVCLIP', }
47 self.crImageStatKeywords = {'CR_NOISE': 'STDEV', } # noqa F841
48 self.metadataStatKeywords = {'RESIDUAL STDEV': 'AMP', } # noqa F841
51class CpVerifyBiasTask(CpVerifyStatsTask):
52 """Bias verification sub-class, implementing the verify method.
53 """
54 ConfigClass = CpVerifyBiasConfig
55 _DefaultName = 'cpVerifyBias'
57 def imageStatistics(self, exposure, uncorrectedExposure, statControl):
58 # Docstring inherited
59 outputStatistics = super().imageStatistics(exposure, uncorrectedExposure, statControl)
61 boxSize = self.config.ampCornerBoxSize
62 statisticToRun = afwMath.stringToStatisticsProperty("MEAN")
64 for ampIdx, amp in enumerate(exposure.getDetector()):
65 ampName = amp.getName()
67 bbox = amp.getBBox()
68 xmin = bbox.getMaxX() - boxSize if amp.getRawFlipX() else bbox.getMinX()
69 ymin = bbox.getMaxY() - boxSize if amp.getRawFlipY() else bbox.getMinY()
70 llc = Point2I(xmin, ymin)
71 extent = Extent2I(boxSize, boxSize)
72 cornerBox = Box2I(llc, extent)
73 cornerExp = exposure[cornerBox]
75 stats = afwMath.makeStatistics(
76 cornerExp.getMaskedImage(), statisticToRun, statControl
77 )
78 outputStatistics[ampName]['AMP_CORNER'] = stats.getValue()
80 return outputStatistics
82 def verify(self, exposure, statisticsDict):
83 """Verify that the measured statistics meet the verification criteria.
85 Parameters
86 ----------
87 exposure : `lsst.afw.image.Exposure`
88 The exposure the statistics are from.
89 statisticsDictionary : `dict` [`str`, `dict` [`str`, scalar]],
90 Dictionary of measured statistics. The inner dictionary
91 should have keys that are statistic names (`str`) with
92 values that are some sort of scalar (`int` or `float` are
93 the mostly likely types).
95 Returns
96 -------
97 outputStatistics : `dict` [`str`, `dict` [`str`, `bool`]]
98 A dictionary indexed by the amplifier name, containing
99 dictionaries of the verification criteria.
100 success : `bool`
101 A boolean indicating if all tests have passed.
102 """
103 detector = exposure.getDetector()
104 ampStats = statisticsDict['AMP']
105 metadataStats = statisticsDict['METADATA']
107 verifyStats = {}
108 success = True
109 for ampName, stats in ampStats.items():
110 verify = {}
112 # DMTN-101 Test 4.2: Mean is 0.0 within noise.
113 verify['MEAN'] = bool(np.abs(stats['MEAN']) < stats['NOISE'])
115 # DMTN-101 Test 4.3: Clipped mean matches readNoise. This
116 # test should use the nominal detector read noise. The
117 # f"RESIDUAL STDEV {ampName}" metadata entry contains the
118 # measured dispersion in the overscan-corrected overscan
119 # region, which should provide an estimate of the read
120 # noise. However, directly using this value will cause
121 # some fraction of verification runs to fail if the
122 # scatter in read noise values is comparable to the test
123 # threshold, as the overscan residual measured may be
124 # sampling from the low end tail of the distribution.
125 # This measurement is also likely to be smaller than that
126 # measured on the bulk of the image as the overscan
127 # correction should be an optimal fit to the overscan
128 # region, but not necessarily for the image region.
129 readNoise = detector[ampName].getReadNoise()
130 verify['NOISE'] = bool((stats['NOISE'] - readNoise)/readNoise <= 0.05)
132 # DMTN-101 Test 4.4: CR rejection matches clipped mean.
133 verify['CR_NOISE'] = bool(np.abs(stats['NOISE'] - stats['CR_NOISE'])/stats['CR_NOISE'] <= 0.05)
135 # Confirm this hasn't triggered a raise condition.
136 if 'FORCE_FAILURE' in stats:
137 verify['PROCESSING'] = False
139 verify['SUCCESS'] = bool(np.all(list(verify.values())))
140 if verify['SUCCESS'] is False:
141 success = False
143 # After determining the verification status for this
144 # exposure, we can also check to see how well the read
145 # noise measured from the overscan residual matches the
146 # nominal value used above in Test 4.3. If these disagree
147 # consistently and significantly, then the assumptions
148 # used in that test may be incorrect, and the nominal read
149 # noise may need recalculation. Only perform this check
150 # if the metadataStats contain the required entry.
151 if 'RESIDUAL STDEV' in metadataStats and ampName in metadataStats['RESIDUAL STDEV']:
152 verify['READ_NOISE_CONSISTENT'] = True
153 overscanReadNoise = metadataStats['RESIDUAL STDEV'][ampName]
154 if overscanReadNoise:
155 if ((overscanReadNoise - readNoise)/readNoise > 0.05):
156 verify['READ_NOISE_CONSISTENT'] = False
158 verifyStats[ampName] = verify
160 return {'AMP': verifyStats}, bool(success)