Coverage for python/lsst/cp/verify/verifyBias.py: 20%

57 statements  

« prev     ^ index     » next       coverage.py v7.5.0, created at 2024-04-24 04:13 -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 

22 

23import lsst.afw.math as afwMath 

24 

25from lsst.geom import Point2I, Extent2I, Box2I 

26from lsst.pex.config import Field 

27from .verifyStats import CpVerifyStatsConfig, CpVerifyStatsTask, CpVerifyStatsConnections 

28 

29__all__ = ['CpVerifyBiasConfig', 'CpVerifyBiasTask'] 

30 

31 

32class CpVerifyBiasConfig(CpVerifyStatsConfig, 

33 pipelineConnections=CpVerifyStatsConnections): 

34 """Inherits from base CpVerifyStatsConfig. 

35 """ 

36 

37 ampCornerBoxSize = Field( 

38 dtype=int, 

39 doc="Size of box to use for measure corner signal.", 

40 default=200, 

41 ) 

42 

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 

49 

50 

51class CpVerifyBiasTask(CpVerifyStatsTask): 

52 """Bias verification sub-class, implementing the verify method. 

53 """ 

54 ConfigClass = CpVerifyBiasConfig 

55 _DefaultName = 'cpVerifyBias' 

56 

57 def imageStatistics(self, exposure, uncorrectedExposure, statControl): 

58 # Docstring inherited 

59 outputStatistics = super().imageStatistics(exposure, uncorrectedExposure, statControl) 

60 

61 boxSize = self.config.ampCornerBoxSize 

62 statisticToRun = afwMath.stringToStatisticsProperty("MEAN") 

63 

64 for ampIdx, amp in enumerate(exposure.getDetector()): 

65 ampName = amp.getName() 

66 

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] 

74 

75 stats = afwMath.makeStatistics( 

76 cornerExp.getMaskedImage(), statisticToRun, statControl 

77 ) 

78 outputStatistics[ampName]['AMP_CORNER'] = stats.getValue() 

79 

80 return outputStatistics 

81 

82 def verify(self, exposure, statisticsDict): 

83 """Verify that the measured statistics meet the verification criteria. 

84 

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). 

94 

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'] 

106 

107 verifyStats = {} 

108 success = True 

109 for ampName, stats in ampStats.items(): 

110 verify = {} 

111 

112 # DMTN-101 Test 4.2: Mean is 0.0 within noise. 

113 verify['MEAN'] = bool(np.abs(stats['MEAN']) < stats['NOISE']) 

114 

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) 

131 

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) 

134 

135 # Confirm this hasn't triggered a raise condition. 

136 if 'FORCE_FAILURE' in stats: 

137 verify['PROCESSING'] = False 

138 

139 verify['SUCCESS'] = bool(np.all(list(verify.values()))) 

140 if verify['SUCCESS'] is False: 

141 success = False 

142 

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 

157 

158 verifyStats[ampName] = verify 

159 

160 return {'AMP': verifyStats}, bool(success)