Coverage for python/lsst/cp/pipe/pdCorrection.py: 19%
65 statements
« prev ^ index » next coverage.py v7.4.3, created at 2024-02-28 12:45 +0000
« prev ^ index » next coverage.py v7.4.3, created at 2024-02-28 12:45 +0000
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
24import lsst.pipe.base as pipeBase
25import lsst.pipe.base.connectionTypes as cT
27from lsst.ip.isr import (PhotodiodeCorrection, IsrProvenance)
29__all__ = ["PhotodiodeCorrectionTask", "PhotodiodeCorrectionConfig"]
32class PhotodiodeCorrectionConnections(pipeBase.PipelineTaskConnections,
33 dimensions=("instrument", )):
35 camera = cT.PrerequisiteInput(
36 name="camera",
37 doc="Camera Geometry definition.",
38 storageClass="Camera",
39 dimensions=("instrument", ),
40 isCalibration=True,
41 )
43 inputPtc = cT.PrerequisiteInput(
44 name="ptc",
45 doc="Input PTC dataset.",
46 storageClass="PhotonTransferCurveDataset",
47 dimensions=("instrument", "detector"),
48 multiple=True,
49 isCalibration=True,
50 )
52 inputLinearizer = cT.Input(
53 name="unCorrectedLinearizer",
54 doc="Raw linearizers that have not been corrected.",
55 storageClass="Linearizer",
56 dimensions=("instrument", "detector"),
57 multiple=True,
58 isCalibration=True,
59 )
61 outputPhotodiodeCorrection = cT.Output(
62 name="pdCorrection",
63 doc="Correction of photodiode systematic error.",
64 storageClass="IsrCalib",
65 dimensions=("instrument", ),
66 isCalibration=True,
67 )
70class PhotodiodeCorrectionConfig(pipeBase.PipelineTaskConfig,
71 pipelineConnections=PhotodiodeCorrectionConnections):
72 """Configuration for calculating the photodiode corrections.
73 """
76class PhotodiodeCorrectionTask(pipeBase.PipelineTask):
77 """Calculate the photodiode corrections.
78 """
80 ConfigClass = PhotodiodeCorrectionConfig
81 _DefaultName = 'cpPhotodiodeCorrection'
83 def runQuantum(self, butlerQC, inputRefs, outputRefs):
84 """Ensure that the input and output dimensions are passed along.
86 Parameters
87 ----------
88 butlerQC : `lsst.daf.butler.QuantumContext`
89 Butler to operate on.
90 inputRefs : `lsst.pipe.base.InputQuantizedConnection`
91 Input data refs to load.
92 outputRefs : `lsst.pipe.base.OutputQuantizedConnection`
93 Output data refs to persist.
94 """
95 inputs = butlerQC.get(inputRefs)
97 # Use the dimensions to set calib/provenance information.
98 inputs['inputDims'] = dict(inputRefs.inputPtc[0].dataId.required)
100 # Need to generate a joint list of detectors present in both
101 # inputPtc and inputLinearizer. We do this here because the
102 # detector info is not present in inputPtc metadata. We could
103 # move it when that is fixed.
105 self.detectorList = []
106 for i, lin in enumerate(inputRefs.inputLinearizer):
107 linDetector = lin.dataId["detector"]
108 for j, ptc in enumerate(inputRefs.inputPtc):
109 ptcDetector = ptc.dataId["detector"]
110 if ptcDetector == linDetector:
111 self.detectorList.append((linDetector, i, j))
112 break
114 outputs = self.run(**inputs)
115 butlerQC.put(outputs, outputRefs)
117 def run(self, inputPtc, inputLinearizer, camera, inputDims):
118 """Calculate the systematic photodiode correction.
120 Parameters
121 ----------
122 inputPtc : `lsst.ip.isr.PtcDataset`
123 Pre-measured PTC dataset.
124 inputLinearizer : `lsst.ip.isr.Linearizer`
125 Previously measured linearizer.
126 camera : `lsst.afw.cameraGeom.Camera`
127 Camera geometry.
128 inputDims : `lsst.daf.butler.DataCoordinate` or `dict`
129 DataIds to use to populate the output calibration.
131 Returns
132 -------
133 results : `lsst.pipe.base.Struct`
134 The results struct containing:
136 ``outputCorrection``
137 Final correction calibration
138 (`lsst.ip.isr.PhotodiodeCorrection`).
139 ``outputProvenance``
140 Provenance data for the new calibration
141 (`lsst.ip.isr.IsrProvenance`).
143 Notes
144 -----
145 Basic correction algorithm (due to Aaron Roodman) is
146 as follows:
147 (1) Run the spline fit to the flux vs monitor diode.
148 (2) For each amp and each exposure, calculate the
149 correction needed to the monitor diode reading to
150 bring it to the spline. We call this the
151 abscissaCorrection.
152 (3) For each exposure, take the median correction
153 across the focal plane. Random variations will cancel
154 out, but systematic variations will not.
155 (4) Subtract this correction from each monitor diode
156 reading.
157 (5) Re-run the spline fit using the corrected monitor
158 diode readings.
159 """
160 # Initialize photodiodeCorrection.
161 photodiodeCorrection = PhotodiodeCorrection(log=self.log)
163 abscissaCorrections = {}
164 # Load all of the corrections, keyed by exposure pair
165 for (detector, linIndex, ptcIndex) in self.detectorList:
166 try:
167 thisLinearizer = inputLinearizer[linIndex]
168 thisPtc = inputPtc[ptcIndex]
169 except (RuntimeError, OSError):
170 continue
172 for amp in camera[detector].getAmplifiers():
173 ampName = amp.getName()
174 fluxResidual = thisLinearizer.fitResiduals[ampName]
175 linearSlope = thisLinearizer.linearFit[ampName]
176 if np.isnan(linearSlope[1]):
177 abscissaCorrection = np.zeros(len(fluxResidual))
178 elif linearSlope[1] < 1.0E-12:
179 abscissaCorrection = np.zeros(len(fluxResidual))
180 else:
181 abscissaCorrection = fluxResidual / linearSlope[1]
182 for i, pair in enumerate(thisPtc.inputExpIdPairs[ampName]):
183 key = str(pair[0])
184 try:
185 abscissaCorrections[key].append(abscissaCorrection[i])
186 except KeyError:
187 abscissaCorrections[key] = []
188 abscissaCorrections[key].append(abscissaCorrection[i])
189 # Now the correction is the median correction
190 # across the whole focal plane.
191 for key in abscissaCorrections.keys():
192 correction = np.nanmedian(abscissaCorrections[key])
193 if np.isnan(correction):
194 correction = 0.0
195 abscissaCorrections[key] = correction
196 photodiodeCorrection.abscissaCorrections = abscissaCorrections
198 photodiodeCorrection.validate()
199 photodiodeCorrection.updateMetadataFromExposures(inputPtc)
200 photodiodeCorrection.updateMetadataFromExposures(inputLinearizer)
201 photodiodeCorrection.updateMetadata(camera=camera, filterName='NONE')
202 photodiodeCorrection.updateMetadata(setDate=True, setCalibId=True)
203 provenance = IsrProvenance(calibType='photodiodeCorrection')
205 return pipeBase.Struct(
206 outputPhotodiodeCorrection=photodiodeCorrection,
207 outputProvenance=provenance,
208 )