Coverage for python/lsst/cp/pipe/pdCorrection.py: 20%

66 statements  

« prev     ^ index     » next       coverage.py v7.2.5, created at 2023-05-13 03:01 -0700

1# This file is part of cp_pipe. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (https://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 <https://www.gnu.org/licenses/>. 

21# 

22import numpy as np 

23 

24import lsst.pipe.base as pipeBase 

25import lsst.pipe.base.connectionTypes as cT 

26 

27from lsst.ip.isr import (PhotodiodeCorrection, IsrProvenance) 

28from ._lookupStaticCalibration import lookupStaticCalibration 

29 

30__all__ = ["PhotodiodeCorrectionTask", "PhotodiodeCorrectionConfig"] 

31 

32 

33class PhotodiodeCorrectionConnections(pipeBase.PipelineTaskConnections, 

34 dimensions=("instrument", )): 

35 

36 camera = cT.PrerequisiteInput( 

37 name="camera", 

38 doc="Camera Geometry definition.", 

39 storageClass="Camera", 

40 dimensions=("instrument", ), 

41 isCalibration=True, 

42 lookupFunction=lookupStaticCalibration, 

43 ) 

44 

45 inputPtc = cT.PrerequisiteInput( 

46 name="ptc", 

47 doc="Input PTC dataset.", 

48 storageClass="PhotonTransferCurveDataset", 

49 dimensions=("instrument", "detector"), 

50 multiple=True, 

51 isCalibration=True, 

52 ) 

53 

54 inputLinearizer = cT.Input( 

55 name="unCorrectedLinearizer", 

56 doc="Raw linearizers that have not been corrected.", 

57 storageClass="Linearizer", 

58 dimensions=("instrument", "detector"), 

59 multiple=True, 

60 isCalibration=True, 

61 ) 

62 

63 outputPhotodiodeCorrection = cT.Output( 

64 name="pdCorrection", 

65 doc="Correction of photodiode systematic error.", 

66 storageClass="IsrCalib", 

67 dimensions=("instrument", ), 

68 isCalibration=True, 

69 ) 

70 

71 

72class PhotodiodeCorrectionConfig(pipeBase.PipelineTaskConfig, 

73 pipelineConnections=PhotodiodeCorrectionConnections): 

74 """Configuration for calculating the photodiode corrections. 

75 """ 

76 

77 

78class PhotodiodeCorrectionTask(pipeBase.PipelineTask): 

79 """Calculate the photodiode corrections. 

80 """ 

81 

82 ConfigClass = PhotodiodeCorrectionConfig 

83 _DefaultName = 'cpPhotodiodeCorrection' 

84 

85 def runQuantum(self, butlerQC, inputRefs, outputRefs): 

86 """Ensure that the input and output dimensions are passed along. 

87 

88 Parameters 

89 ---------- 

90 butlerQC : `lsst.daf.butler.butlerQuantumContext.ButlerQuantumContext` 

91 Butler to operate on. 

92 inputRefs : `lsst.pipe.base.connections.InputQuantizedConnection` 

93 Input data refs to load. 

94 outputRefs : `lsst.pipe.base.connections.OutputQuantizedConnection` 

95 Output data refs to persist. 

96 """ 

97 inputs = butlerQC.get(inputRefs) 

98 

99 # Use the dimensions to set calib/provenance information. 

100 inputs['inputDims'] = inputRefs.inputPtc[0].dataId.byName() 

101 

102 # Need to generate a joint list of detectors present in both 

103 # inputPtc and inputLinearizer. We do this here because the 

104 # detector info is not present in inputPtc metadata. We could 

105 # move it when that is fixed. 

106 

107 self.detectorList = [] 

108 for i, lin in enumerate(inputRefs.inputLinearizer): 

109 linDetector = lin.dataId["detector"] 

110 for j, ptc in enumerate(inputRefs.inputPtc): 

111 ptcDetector = ptc.dataId["detector"] 

112 if ptcDetector == linDetector: 

113 self.detectorList.append((linDetector, i, j)) 

114 break 

115 

116 outputs = self.run(**inputs) 

117 butlerQC.put(outputs, outputRefs) 

118 

119 def run(self, inputPtc, inputLinearizer, camera, inputDims): 

120 """Calculate the systematic photodiode correction. 

121 

122 Parameters 

123 ---------- 

124 inputPtc : `lsst.ip.isr.PtcDataset` 

125 Pre-measured PTC dataset. 

126 inputLinearizer : `lsst.ip.isr.Linearizer` 

127 Previously measured linearizer. 

128 camera : `lsst.afw.cameraGeom.Camera` 

129 Camera geometry. 

130 inputDims : `lsst.daf.butler.DataCoordinate` or `dict` 

131 DataIds to use to populate the output calibration. 

132 

133 Returns 

134 ------- 

135 results : `lsst.pipe.base.Struct` 

136 The results struct containing: 

137 

138 ``outputCorrection`` 

139 Final correction calibration 

140 (`lsst.ip.isr.PhotodiodeCorrection`). 

141 ``outputProvenance`` 

142 Provenance data for the new calibration 

143 (`lsst.ip.isr.IsrProvenance`). 

144 

145 Notes 

146 ----- 

147 Basic correction algorithm (due to Aaron Roodman) is 

148 as follows: 

149 (1) Run the spline fit to the flux vs monitor diode. 

150 (2) For each amp and each exposure, calculate the 

151 correction needed to the monitor diode reading to 

152 bring it to the spline. We call this the 

153 abscissaCorrection. 

154 (3) For each exposure, take the median correction 

155 across the focal plane. Random variations will cancel 

156 out, but systematic variations will not. 

157 (4) Subtract this correction from each monitor diode 

158 reading. 

159 (5) Re-run the spline fit using the corrected monitor 

160 diode readings. 

161 """ 

162 # Initialize photodiodeCorrection. 

163 photodiodeCorrection = PhotodiodeCorrection(log=self.log) 

164 

165 abscissaCorrections = {} 

166 # Load all of the corrections, keyed by exposure pair 

167 for (detector, linIndex, ptcIndex) in self.detectorList: 

168 try: 

169 thisLinearizer = inputLinearizer[linIndex] 

170 thisPtc = inputPtc[ptcIndex] 

171 except (RuntimeError, OSError): 

172 continue 

173 

174 for amp in camera[detector].getAmplifiers(): 

175 ampName = amp.getName() 

176 fluxResidual = thisLinearizer.fitResiduals[ampName] 

177 linearSlope = thisLinearizer.linearFit[ampName] 

178 if np.isnan(linearSlope[1]): 

179 abscissaCorrection = np.zeros(len(fluxResidual)) 

180 elif linearSlope[1] < 1.0E-12: 

181 abscissaCorrection = np.zeros(len(fluxResidual)) 

182 else: 

183 abscissaCorrection = fluxResidual / linearSlope[1] 

184 for i, pair in enumerate(thisPtc.inputExpIdPairs[ampName]): 

185 key = str(pair[0]) 

186 try: 

187 abscissaCorrections[key].append(abscissaCorrection[i]) 

188 except KeyError: 

189 abscissaCorrections[key] = [] 

190 abscissaCorrections[key].append(abscissaCorrection[i]) 

191 # Now the correction is the median correction 

192 # across the whole focal plane. 

193 for key in abscissaCorrections.keys(): 

194 correction = np.nanmedian(abscissaCorrections[key]) 

195 if np.isnan(correction): 

196 correction = 0.0 

197 abscissaCorrections[key] = correction 

198 photodiodeCorrection.abscissaCorrections = abscissaCorrections 

199 

200 photodiodeCorrection.validate() 

201 photodiodeCorrection.updateMetadataFromExposures(inputPtc) 

202 photodiodeCorrection.updateMetadataFromExposures(inputLinearizer) 

203 photodiodeCorrection.updateMetadata(camera=camera, filterName='NONE') 

204 photodiodeCorrection.updateMetadata(setDate=True, setCalibId=True) 

205 provenance = IsrProvenance(calibType='photodiodeCorrection') 

206 

207 return pipeBase.Struct( 

208 outputPhotodiodeCorrection=photodiodeCorrection, 

209 outputProvenance=provenance, 

210 )