28 from .photodiode
import getBOTphotodiodeData
35 __all__ = [
'MeasurePhotonTransferCurveTask',
'MeasurePhotonTransferCurveTaskConfig']
39 extract = pexConfig.ConfigurableField(
40 target=PhotonTransferCurveExtractTask,
41 doc=
"Task to measure covariances from flats.",
43 solve = pexConfig.ConfigurableField(
44 target=PhotonTransferCurveSolveTask,
45 doc=
"Task to fit models to the measured covariances.",
47 ccdKey = pexConfig.Field(
49 doc=
"The key by which to pull a detector from a dataId, e.g. 'ccd' or 'detector'.",
52 doPhotodiode = pexConfig.Field(
54 doc=
"Apply a correction based on the photodiode readings if available?",
57 photodiodeDataPath = pexConfig.Field(
59 doc=
"Gen2 only: path to locate the data photodiode data files.",
65 """A class to calculate, fit, and plot a PTC from a set of flat pairs.
66 The Photon Transfer Curve (var(signal) vs mean(signal)) is a standard
67 tool used in astronomical detectors characterization (e.g., Janesick 2001,
68 Janesick 2007). If ptcFitType is "EXPAPPROXIMATION" or "POLYNOMIAL",
69 this task calculates the PTC from a series of pairs of flat-field images;
70 each pair taken at identical exposure times. The difference image of each
71 pair is formed to eliminate fixed pattern noise, and then the variance
72 of the difference image and the mean of the average image
73 are used to produce the PTC. An n-degree polynomial or the approximation
74 in Equation 16 of Astier+19 ("The Shape of the Photon Transfer Curve
75 of CCD sensors", arXiv:1905.08677) can be fitted to the PTC curve. These
76 models include parameters such as the gain (e/DN) and readout noise.
77 Linearizers to correct for signal-chain non-linearity are also calculated.
78 The `Linearizer` class, in general, can support per-amp linearizers, but
79 in this task this is not supported.
80 If ptcFitType is "FULLCOVARIANCE", the covariances of the difference
81 images are calculated via the DFT methods described in Astier+19 and the
82 variances for the PTC are given by the cov[0,0] elements at each signal
83 level. The full model in Equation 20 of Astier+19 is fit to the PTC
84 to get the gain and the noise.
89 Positional arguments passed to the Task constructor. None used at this
92 Keyword arguments passed on to the Task constructor. None used at this
96 RunnerClass = DataRefListRunner
97 ConfigClass = MeasurePhotonTransferCurveTaskConfig
98 _DefaultName =
"measurePhotonTransferCurve"
102 self.makeSubtask(
"extract")
103 self.makeSubtask(
"solve")
107 """Run the Photon Transfer Curve (PTC) measurement task.
108 For a dataRef (which is each detector here),
109 and given a list of exposure pairs (postISR) at different exposure times,
114 dataRefList : `list` [`lsst.daf.peristence.ButlerDataRef`]
115 Data references for exposures.
117 if len(dataRefList) < 2:
118 raise RuntimeError(
"Insufficient inputs to combine.")
121 dataRef = dataRefList[0]
122 camera = dataRef.get(
'camera')
124 if len(
set([dataRef.dataId[self.config.ccdKey]
for dataRef
in dataRefList])) > 1:
125 raise RuntimeError(
"Too many detectors supplied")
128 for dataRef
in dataRefList:
130 tempFlat = dataRef.get(
"postISRCCD")
132 self.log.
warn(
"postISR exposure could not be retrieved. Ignoring flat.")
134 expList.append(tempFlat)
135 expIds = [exp.getInfo().getVisitInfo().
getExposureId()
for exp
in expList]
140 resultsExtract = self.extract.
run(inputExp=expDict, inputDims=expIds)
141 resultsSolve = self.solve.
run(resultsExtract.outputCovariances, camera=camera)
146 ampNames = resultsSolve.outputPtcDataset.ampNames
147 for ampName
in ampNames:
148 tempAmpName = ampName
149 if ampName
not in resultsSolve.outputPtcDataset.badAmps:
151 for pair
in resultsSolve.outputPtcDataset.inputExpIdPairs[tempAmpName]:
152 first, second = pair[0]
153 expIdsPairsList.append((first, second))
155 resultsSolve.outputPtcDataset = self.
_setBOTPhotocharge_setBOTPhotocharge(dataRef, resultsSolve.outputPtcDataset,
157 self.log.
info(
"Writing PTC data.")
158 dataRef.put(resultsSolve.outputPtcDataset, datasetType=
"photonTransferCurveDataset")
162 def _setBOTPhotocharge(self, dataRef, datasetPtc, expIdList):
163 """Set photoCharge attribute in PTC dataset
167 dataRef : `lsst.daf.peristence.ButlerDataRef`
168 Data reference for exposurre for detector to process.
170 datasetPtc : `lsst.ip.isr.ptcDataset.PhotonTransferCurveDataset`
171 The dataset containing information such as the means, variances
175 List with exposure pairs Ids (one pair per list entry).
179 datasetPtc: `lsst.ip.isr.ptcDataset.PhotonTransferCurveDataset`
180 This is the same dataset as the input parameter, however,
181 it has been modified to update the datasetPtc.photoCharge
184 if self.config.doPhotodiode:
185 for (expId1, expId2)
in expIdList:
187 for i, expId
in enumerate([expId1, expId2]):
192 dataRef.dataId[
'expId'] = expId//1000
193 if self.config.photodiodeDataPath:
198 charges[i] = photodiodeData.getCharge()
202 self.log.
warn(f
"No photodiode data found for {expId}")
204 for ampName
in datasetPtc.ampNames:
205 datasetPtc.photoCharge[ampName].
append((charges[0], charges[1]))
209 for ampName
in datasetPtc.ampNames:
210 datasetPtc.photoCharge[ampName] = np.repeat(np.nan, len(expIdList))
def _setBOTPhotocharge(self, dataRef, datasetPtc, expIdList)
def __init__(self, *args, **kwargs)
def runDataRef(self, dataRefList)
daf::base::PropertySet * set
std::shared_ptr< FrameSet > append(FrameSet const &first, FrameSet const &second)
Construct a FrameSet that performs two transformations in series.
def getBOTphotodiodeData(dataRef, dataPath='/project/shared/BOT/_parent/raw/photodiode_data/', logger=None)
def arrangeFlatsByExpTime(exposureList, exposureIdList)
def getExposureId(self, dataRef)
def run(self, skyInfo, tempExpRefList, imageScalerList, weightList, altMaskList=None, mask=None, supplementaryData=None)