Coverage for python/lsst/ip/isr/ptcDataset.py: 7%
263 statements
« prev ^ index » next coverage.py v7.2.3, created at 2023-04-19 04:27 -0700
« prev ^ index » next coverage.py v7.2.3, created at 2023-04-19 04:27 -0700
1#
2# LSST Data Management System
3# Copyright 2008-2017 AURA/LSST.
4#
5# This product includes software developed by the
6# LSST Project (http://www.lsst.org/).
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the LSST License Statement and
19# the GNU General Public License along with this program. If not,
20# see <https://www.lsstcorp.org/LegalNotices/>.
21#
22"""
23Define dataset class for MeasurePhotonTransferCurve task
24"""
26__all__ = ['PhotonTransferCurveDataset']
28import numpy as np
29from astropy.table import Table
30import warnings
32from lsst.ip.isr import IsrCalib
35class PhotonTransferCurveDataset(IsrCalib):
36 """A simple class to hold the output data from the PTC task.
38 The dataset is made up of a dictionary for each item, keyed by the
39 amplifiers' names, which much be supplied at construction time.
40 New items cannot be added to the class to save accidentally saving to the
41 wrong property, and the class can be frozen if desired.
42 inputExpIdPairs records the exposures used to produce the data.
43 When fitPtc() or fitCovariancesAstier() is run, a mask is built up, which
44 is by definition always the same length as inputExpIdPairs, rawExpTimes,
45 rawMeans and rawVars, and is a list of bools, which are incrementally set
46 to False as points are discarded from the fits.
47 PTC fit parameters for polynomials are stored in a list in ascending order
48 of polynomial term, i.e. par[0]*x^0 + par[1]*x + par[2]*x^2 etc
49 with the length of the list corresponding to the order of the polynomial
50 plus one.
52 Parameters
53 ----------
54 ampNames : `list`
55 List with the names of the amplifiers of the detector at hand.
56 ptcFitType : `str`
57 Type of model fitted to the PTC: "POLYNOMIAL", "EXPAPPROXIMATION",
58 or "FULLCOVARIANCE".
59 covMatrixSide : `int`
60 Maximum lag of covariances (size of square covariance matrices).
61 kwargs : `dict`, optional
62 Other keyword arguments to pass to the parent init.
64 Notes
65 -----
66 The stored attributes are:
68 badAmps : `list` [`str`]
69 List with bad amplifiers names.
70 inputExpIdPairs : `dict`, [`str`, `list`]
71 Dictionary keyed by amp names containing the input exposures IDs.
72 expIdMask : `dict`, [`str`, `np.ndarray`]
73 Dictionary keyed by amp names containing the mask produced after
74 outlier rejection. The mask produced by the "FULLCOVARIANCE"
75 option may differ from the one produced in the other two PTC
76 fit types.
77 rawExpTimes : `dict`, [`str`, `np.ndarray`]
78 Dictionary keyed by amp names containing the unmasked exposure times.
79 rawMeans : `dict`, [`str`, `np.ndarray`]
80 Dictionary keyed by amp namescontaining the unmasked average of the
81 means of the exposures in each flat pair.
82 rawVars : `dict`, [`str`, `np.ndarray`]
83 Dictionary keyed by amp names containing the variance of the
84 difference image of the exposures in each flat pair.
85 gain : `dict`, [`str`, `float`]
86 Dictionary keyed by amp names containing the fitted gains.
87 gainErr : `dict`, [`str`, `float`]
88 Dictionary keyed by amp names containing the errors on the
89 fitted gains.
90 noise : `dict`, [`str`, `float`]
91 Dictionary keyed by amp names containing the fitted noise.
92 noiseErr : `dict`, [`str`, `float`]
93 Dictionary keyed by amp names containing the errors on the fitted
94 noise.
95 ptcFitPars : `dict`, [`str`, `np.ndarray`]
96 Dictionary keyed by amp names containing the fitted parameters of the
97 PTC model for ptcFitTye in ["POLYNOMIAL", "EXPAPPROXIMATION"].
98 ptcFitParsError : `dict`, [`str`, `np.ndarray`]
99 Dictionary keyed by amp names containing the errors on the fitted
100 parameters of the PTC model for ptcFitTye in
101 ["POLYNOMIAL", "EXPAPPROXIMATION"].
102 ptcFitChiSq : `dict`, [`str`, `float`]
103 Dictionary keyed by amp names containing the reduced chi squared
104 of the fit for ptcFitTye in ["POLYNOMIAL", "EXPAPPROXIMATION"].
105 ptcTurnoff : `dict` [`str, `float`]
106 Flux value (in ADU) where the variance of the PTC curve starts
107 decreasing consistently.
108 covariances : `dict`, [`str`, `np.ndarray`]
109 Dictionary keyed by amp names containing a list of measured
110 covariances per mean flux.
111 covariancesModel : `dict`, [`str`, `np.ndarray`]
112 Dictionary keyed by amp names containinging covariances model
113 (Eq. 20 of Astier+19) per mean flux.
114 covariancesSqrtWeights : `dict`, [`str`, `np.ndarray`]
115 Dictionary keyed by amp names containinging sqrt. of covariances
116 weights.
117 aMatrix : `dict`, [`str`, `np.ndarray`]
118 Dictionary keyed by amp names containing the "a" parameters from
119 the model in Eq. 20 of Astier+19.
120 bMatrix : `dict`, [`str`, `np.ndarray`]
121 Dictionary keyed by amp names containing the "b" parameters from
122 the model in Eq. 20 of Astier+19.
123 covariancesModelNoB : `dict`, [`str`, `np.ndarray`]
124 Dictionary keyed by amp names containing covariances model
125 (with 'b'=0 in Eq. 20 of Astier+19)
126 per mean flux.
127 aMatrixNoB : `dict`, [`str`, `np.ndarray`]
128 Dictionary keyed by amp names containing the "a" parameters from the
129 model in Eq. 20 of Astier+19
130 (and 'b' = 0).
131 finalVars : `dict`, [`str`, `np.ndarray`]
132 Dictionary keyed by amp names containing the masked variance of the
133 difference image of each flat
134 pair. If needed, each array will be right-padded with
135 np.nan to match the length of rawExpTimes.
136 finalModelVars : `dict`, [`str`, `np.ndarray`]
137 Dictionary keyed by amp names containing the masked modeled
138 variance of the difference image of each flat pair. If needed, each
139 array will be right-padded with np.nan to match the length of
140 rawExpTimes.
141 finalMeans : `dict`, [`str`, `np.ndarray`]
142 Dictionary keyed by amp names containing the masked average of the
143 means of the exposures in each flat pair. If needed, each array
144 will be right-padded with np.nan to match the length of
145 rawExpTimes.
146 photoCharges : `dict`, [`str`, `np.ndarray`]
147 Dictionary keyed by amp names containing the integrated photocharge
148 for linearity calibration.
150 Version 1.1 adds the `ptcTurnoff` attribute.
151 """
153 _OBSTYPE = 'PTC'
154 _SCHEMA = 'Gen3 Photon Transfer Curve'
155 _VERSION = 1.1
157 def __init__(self, ampNames=[], ptcFitType=None, covMatrixSide=1, **kwargs):
158 self.ptcFitType = ptcFitType
159 self.ampNames = ampNames
160 self.covMatrixSide = covMatrixSide
162 self.badAmps = []
164 self.inputExpIdPairs = {ampName: [] for ampName in ampNames}
165 self.expIdMask = {ampName: np.array([], dtype=bool) for ampName in ampNames}
166 self.rawExpTimes = {ampName: np.array([]) for ampName in ampNames}
167 self.rawMeans = {ampName: np.array([]) for ampName in ampNames}
168 self.rawVars = {ampName: np.array([]) for ampName in ampNames}
169 self.photoCharges = {ampName: np.array([]) for ampName in ampNames}
171 self.gain = {ampName: np.nan for ampName in ampNames}
172 self.gainErr = {ampName: np.nan for ampName in ampNames}
173 self.noise = {ampName: np.nan for ampName in ampNames}
174 self.noiseErr = {ampName: np.nan for ampName in ampNames}
176 self.ptcFitPars = {ampName: np.array([]) for ampName in ampNames}
177 self.ptcFitParsError = {ampName: np.array([]) for ampName in ampNames}
178 self.ptcFitChiSq = {ampName: np.nan for ampName in ampNames}
179 self.ptcTurnoff = {ampName: np.nan for ampName in ampNames}
181 self.covariances = {ampName: np.array([]) for ampName in ampNames}
182 self.covariancesModel = {ampName: np.array([]) for ampName in ampNames}
183 self.covariancesSqrtWeights = {ampName: np.array([]) for ampName in ampNames}
184 self.aMatrix = {ampName: np.array([]) for ampName in ampNames}
185 self.bMatrix = {ampName: np.array([]) for ampName in ampNames}
186 self.covariancesModelNoB = {ampName: np.array([]) for ampName in ampNames}
187 self.aMatrixNoB = {ampName: np.array([]) for ampName in ampNames}
189 self.finalVars = {ampName: np.array([]) for ampName in ampNames}
190 self.finalModelVars = {ampName: np.array([]) for ampName in ampNames}
191 self.finalMeans = {ampName: np.array([]) for ampName in ampNames}
193 super().__init__(**kwargs)
194 self.requiredAttributes.update(['badAmps', 'inputExpIdPairs', 'expIdMask', 'rawExpTimes',
195 'rawMeans', 'rawVars', 'gain', 'gainErr', 'noise', 'noiseErr',
196 'ptcFitPars', 'ptcFitParsError', 'ptcFitChiSq', 'ptcTurnoff',
197 'aMatrixNoB', 'covariances', 'covariancesModel',
198 'covariancesSqrtWeights', 'covariancesModelNoB',
199 'aMatrix', 'bMatrix', 'finalVars', 'finalModelVars', 'finalMeans',
200 'photoCharges'])
202 self.updateMetadata(setCalibInfo=True, setCalibId=True, **kwargs)
204 def setAmpValuesPartialDataset(
205 self,
206 ampName,
207 inputExpIdPair=(-1, -1),
208 rawExpTime=np.nan,
209 rawMean=np.nan,
210 rawVar=np.nan,
211 photoCharge=np.nan,
212 expIdMask=False,
213 covariance=None,
214 covSqrtWeights=None,
215 gain=np.nan,
216 noise=np.nan,
217 ):
218 """
219 Set the amp values for a partial PTC Dataset (from cpExtractPtcTask).
221 Parameters
222 ----------
223 ampName : `str`
224 Name of the amp to set the values.
225 inputExpIdPair : `tuple` [`int`]
226 Exposure IDs of input pair.
227 rawExpTime : `float`, optional
228 Exposure time for this exposure pair.
229 rawMean : `float`, optional
230 Average of the means of the exposures in this pair.
231 rawVar : `float`, optional
232 Variance of the difference of the exposures in this pair.
233 photoCharge : `float`, optional
234 Integrated photocharge for flat pair for linearity calibration.
235 expIdMask : `bool`, optional
236 Flag setting if this exposure pair should be used (True)
237 or not used (False).
238 covariance : `np.ndarray` or None, optional
239 Measured covariance for this exposure pair.
240 covSqrtWeights : `np.ndarray` or None, optional
241 Measured sqrt of covariance weights in this exposure pair.
242 gain : `float`, optional
243 Estimated gain for this exposure pair.
244 noise : `float`, optional
245 Estimated read noise for this exposure pair.
246 """
247 nanMatrix = np.full((self.covMatrixSide, self.covMatrixSide), np.nan)
248 if covariance is None:
249 covariance = nanMatrix
250 if covSqrtWeights is None:
251 covSqrtWeights = nanMatrix
253 self.inputExpIdPairs[ampName] = [inputExpIdPair]
254 self.rawExpTimes[ampName] = np.array([rawExpTime])
255 self.rawMeans[ampName] = np.array([rawMean])
256 self.rawVars[ampName] = np.array([rawVar])
257 self.photoCharges[ampName] = np.array([photoCharge])
258 self.expIdMask[ampName] = np.array([expIdMask])
259 self.covariances[ampName] = np.array([covariance])
260 self.covariancesSqrtWeights[ampName] = np.array([covSqrtWeights])
261 self.gain[ampName] = gain
262 self.noise[ampName] = noise
264 self.covariancesModel[ampName] = np.array([nanMatrix])
265 self.covariancesModelNoB[ampName] = np.array([nanMatrix])
266 self.aMatrix[ampName] = nanMatrix
267 self.bMatrix[ampName] = nanMatrix
268 self.aMatrixNoB[ampName] = nanMatrix
270 def updateMetadata(self, **kwargs):
271 """Update calibration metadata.
272 This calls the base class's method after ensuring the required
273 calibration keywords will be saved.
275 Parameters
276 ----------
277 setDate : `bool`, optional
278 Update the CALIBDATE fields in the metadata to the current
279 time. Defaults to False.
280 kwargs :
281 Other keyword parameters to set in the metadata.
282 """
283 super().updateMetadata(PTC_FIT_TYPE=self.ptcFitType, **kwargs)
285 @classmethod
286 def fromDict(cls, dictionary):
287 """Construct a calibration from a dictionary of properties.
288 Must be implemented by the specific calibration subclasses.
290 Parameters
291 ----------
292 dictionary : `dict`
293 Dictionary of properties.
295 Returns
296 -------
297 calib : `lsst.ip.isr.PhotonTransferCurveDataset`
298 Constructed calibration.
300 Raises
301 ------
302 RuntimeError
303 Raised if the supplied dictionary is for a different
304 calibration.
305 """
306 calib = cls()
307 if calib._OBSTYPE != dictionary['metadata']['OBSTYPE']:
308 raise RuntimeError(f"Incorrect Photon Transfer Curve dataset supplied. "
309 f"Expected {calib._OBSTYPE}, found {dictionary['metadata']['OBSTYPE']}")
310 calib.setMetadata(dictionary['metadata'])
311 calib.ptcFitType = dictionary['ptcFitType']
312 calib.covMatrixSide = dictionary['covMatrixSide']
313 calib.badAmps = np.array(dictionary['badAmps'], 'str').tolist()
314 calib.ampNames = []
316 # The cov matrices are square
317 covMatrixSide = calib.covMatrixSide
318 # Number of final signal levels
319 covDimensionsProduct = len(np.array(list(dictionary['covariances'].values())[0]).ravel())
320 nSignalPoints = int(covDimensionsProduct/(covMatrixSide*covMatrixSide))
322 for ampName in dictionary['ampNames']:
323 calib.ampNames.append(ampName)
324 calib.inputExpIdPairs[ampName] = dictionary['inputExpIdPairs'][ampName]
325 calib.expIdMask[ampName] = np.array(dictionary['expIdMask'][ampName])
326 calib.rawExpTimes[ampName] = np.array(dictionary['rawExpTimes'][ampName], dtype=np.float64)
327 calib.rawMeans[ampName] = np.array(dictionary['rawMeans'][ampName], dtype=np.float64)
328 calib.rawVars[ampName] = np.array(dictionary['rawVars'][ampName], dtype=np.float64)
329 calib.gain[ampName] = float(dictionary['gain'][ampName])
330 calib.gainErr[ampName] = float(dictionary['gainErr'][ampName])
331 calib.noise[ampName] = float(dictionary['noise'][ampName])
332 calib.noiseErr[ampName] = float(dictionary['noiseErr'][ampName])
333 calib.ptcFitPars[ampName] = np.array(dictionary['ptcFitPars'][ampName], dtype=np.float64)
334 calib.ptcFitParsError[ampName] = np.array(dictionary['ptcFitParsError'][ampName],
335 dtype=np.float64)
336 calib.ptcFitChiSq[ampName] = float(dictionary['ptcFitChiSq'][ampName])
337 calib.ptcTurnoff[ampName] = float(dictionary['ptcTurnoff'][ampName])
338 if nSignalPoints > 0:
339 # Regular dataset
340 calib.covariances[ampName] = np.array(dictionary['covariances'][ampName],
341 dtype=np.float64).reshape(
342 (nSignalPoints, covMatrixSide, covMatrixSide))
343 calib.covariancesModel[ampName] = np.array(
344 dictionary['covariancesModel'][ampName],
345 dtype=np.float64).reshape(
346 (nSignalPoints, covMatrixSide, covMatrixSide))
347 calib.covariancesSqrtWeights[ampName] = np.array(
348 dictionary['covariancesSqrtWeights'][ampName],
349 dtype=np.float64).reshape(
350 (nSignalPoints, covMatrixSide, covMatrixSide))
351 calib.aMatrix[ampName] = np.array(dictionary['aMatrix'][ampName],
352 dtype=np.float64).reshape(
353 (covMatrixSide, covMatrixSide))
354 calib.bMatrix[ampName] = np.array(dictionary['bMatrix'][ampName],
355 dtype=np.float64).reshape(
356 (covMatrixSide, covMatrixSide))
357 calib.covariancesModelNoB[ampName] = np.array(
358 dictionary['covariancesModelNoB'][ampName], dtype=np.float64).reshape(
359 (nSignalPoints, covMatrixSide, covMatrixSide))
360 calib.aMatrixNoB[ampName] = np.array(
361 dictionary['aMatrixNoB'][ampName],
362 dtype=np.float64).reshape((covMatrixSide, covMatrixSide))
363 else:
364 # Empty dataset
365 calib.covariances[ampName] = np.array([], dtype=np.float64)
366 calib.covariancesModel[ampName] = np.array([], dtype=np.float64)
367 calib.covariancesSqrtWeights[ampName] = np.array([], dtype=np.float64)
368 calib.aMatrix[ampName] = np.array([], dtype=np.float64)
369 calib.bMatrix[ampName] = np.array([], dtype=np.float64)
370 calib.covariancesModelNoB[ampName] = np.array([], dtype=np.float64)
371 calib.aMatrixNoB[ampName] = np.array([], dtype=np.float64)
373 calib.finalVars[ampName] = np.array(dictionary['finalVars'][ampName], dtype=np.float64)
374 calib.finalModelVars[ampName] = np.array(dictionary['finalModelVars'][ampName], dtype=np.float64)
375 calib.finalMeans[ampName] = np.array(dictionary['finalMeans'][ampName], dtype=np.float64)
376 calib.photoCharges[ampName] = np.array(dictionary['photoCharges'][ampName], dtype=np.float64)
378 calib.updateMetadata()
379 return calib
381 def toDict(self):
382 """Return a dictionary containing the calibration properties.
383 The dictionary should be able to be round-tripped through
384 `fromDict`.
386 Returns
387 -------
388 dictionary : `dict`
389 Dictionary of properties.
390 """
391 self.updateMetadata()
393 outDict = dict()
394 metadata = self.getMetadata()
395 outDict['metadata'] = metadata
397 def _dictOfArraysToDictOfLists(dictOfArrays):
398 dictOfLists = {}
399 for key, value in dictOfArrays.items():
400 dictOfLists[key] = value.ravel().tolist()
402 return dictOfLists
404 outDict['ptcFitType'] = self.ptcFitType
405 outDict['covMatrixSide'] = self.covMatrixSide
406 outDict['ampNames'] = self.ampNames
407 outDict['badAmps'] = self.badAmps
408 outDict['inputExpIdPairs'] = self.inputExpIdPairs
409 outDict['expIdMask'] = _dictOfArraysToDictOfLists(self.expIdMask)
410 outDict['rawExpTimes'] = _dictOfArraysToDictOfLists(self.rawExpTimes)
411 outDict['rawMeans'] = _dictOfArraysToDictOfLists(self.rawMeans)
412 outDict['rawVars'] = _dictOfArraysToDictOfLists(self.rawVars)
413 outDict['gain'] = self.gain
414 outDict['gainErr'] = self.gainErr
415 outDict['noise'] = self.noise
416 outDict['noiseErr'] = self.noiseErr
417 outDict['ptcFitPars'] = _dictOfArraysToDictOfLists(self.ptcFitPars)
418 outDict['ptcFitParsError'] = _dictOfArraysToDictOfLists(self.ptcFitParsError)
419 outDict['ptcFitChiSq'] = self.ptcFitChiSq
420 outDict['ptcTurnoff'] = self.ptcTurnoff
421 outDict['covariances'] = _dictOfArraysToDictOfLists(self.covariances)
422 outDict['covariancesModel'] = _dictOfArraysToDictOfLists(self.covariancesModel)
423 outDict['covariancesSqrtWeights'] = _dictOfArraysToDictOfLists(self.covariancesSqrtWeights)
424 outDict['aMatrix'] = _dictOfArraysToDictOfLists(self.aMatrix)
425 outDict['bMatrix'] = _dictOfArraysToDictOfLists(self.bMatrix)
426 outDict['covariancesModelNoB'] = _dictOfArraysToDictOfLists(self.covariancesModelNoB)
427 outDict['aMatrixNoB'] = _dictOfArraysToDictOfLists(self.aMatrixNoB)
428 outDict['finalVars'] = _dictOfArraysToDictOfLists(self.finalVars)
429 outDict['finalModelVars'] = _dictOfArraysToDictOfLists(self.finalModelVars)
430 outDict['finalMeans'] = _dictOfArraysToDictOfLists(self.finalMeans)
431 outDict['photoCharges'] = _dictOfArraysToDictOfLists(self.photoCharges)
433 return outDict
435 @classmethod
436 def fromTable(cls, tableList):
437 """Construct calibration from a list of tables.
438 This method uses the `fromDict` method to create the
439 calibration, after constructing an appropriate dictionary from
440 the input tables.
442 Parameters
443 ----------
444 tableList : `list` [`lsst.afw.table.Table`]
445 List of tables to use to construct the datasetPtc.
447 Returns
448 -------
449 calib : `lsst.ip.isr.PhotonTransferCurveDataset`
450 The calibration defined in the tables.
451 """
452 ptcTable = tableList[0]
454 metadata = ptcTable.meta
455 inDict = dict()
456 inDict['metadata'] = metadata
457 inDict['ampNames'] = []
458 inDict['ptcFitType'] = []
459 inDict['covMatrixSide'] = []
460 inDict['inputExpIdPairs'] = dict()
461 inDict['expIdMask'] = dict()
462 inDict['rawExpTimes'] = dict()
463 inDict['rawMeans'] = dict()
464 inDict['rawVars'] = dict()
465 inDict['gain'] = dict()
466 inDict['gainErr'] = dict()
467 inDict['noise'] = dict()
468 inDict['noiseErr'] = dict()
469 inDict['ptcFitPars'] = dict()
470 inDict['ptcFitParsError'] = dict()
471 inDict['ptcFitChiSq'] = dict()
472 inDict['ptcTurnoff'] = dict()
473 inDict['covariances'] = dict()
474 inDict['covariancesModel'] = dict()
475 inDict['covariancesSqrtWeights'] = dict()
476 inDict['aMatrix'] = dict()
477 inDict['bMatrix'] = dict()
478 inDict['covariancesModelNoB'] = dict()
479 inDict['aMatrixNoB'] = dict()
480 inDict['finalVars'] = dict()
481 inDict['finalModelVars'] = dict()
482 inDict['finalMeans'] = dict()
483 inDict['badAmps'] = []
484 inDict['photoCharges'] = dict()
486 calibVersion = metadata['PTC_VERSION']
487 if calibVersion == 1.0:
488 cls().log.warning(f"Previous version found for PTC dataset: {calibVersion}. "
489 f"Setting 'ptcTurnoff' in all amps to last value in 'finalMeans'.")
490 for record in ptcTable:
491 ampName = record['AMPLIFIER_NAME']
493 inDict['ptcFitType'] = record['PTC_FIT_TYPE']
494 inDict['covMatrixSide'] = record['COV_MATRIX_SIDE']
495 inDict['ampNames'].append(ampName)
496 inDict['inputExpIdPairs'][ampName] = record['INPUT_EXP_ID_PAIRS'].tolist()
497 inDict['expIdMask'][ampName] = record['EXP_ID_MASK']
498 inDict['rawExpTimes'][ampName] = record['RAW_EXP_TIMES']
499 inDict['rawMeans'][ampName] = record['RAW_MEANS']
500 inDict['rawVars'][ampName] = record['RAW_VARS']
501 inDict['gain'][ampName] = record['GAIN']
502 inDict['gainErr'][ampName] = record['GAIN_ERR']
503 inDict['noise'][ampName] = record['NOISE']
504 inDict['noiseErr'][ampName] = record['NOISE_ERR']
505 inDict['ptcFitPars'][ampName] = record['PTC_FIT_PARS']
506 inDict['ptcFitParsError'][ampName] = record['PTC_FIT_PARS_ERROR']
507 inDict['ptcFitChiSq'][ampName] = record['PTC_FIT_CHI_SQ']
508 inDict['covariances'][ampName] = record['COVARIANCES']
509 inDict['covariancesModel'][ampName] = record['COVARIANCES_MODEL']
510 inDict['covariancesSqrtWeights'][ampName] = record['COVARIANCES_SQRT_WEIGHTS']
511 inDict['aMatrix'][ampName] = record['A_MATRIX']
512 inDict['bMatrix'][ampName] = record['B_MATRIX']
513 inDict['covariancesModelNoB'][ampName] = record['COVARIANCES_MODEL_NO_B']
514 inDict['aMatrixNoB'][ampName] = record['A_MATRIX_NO_B']
515 inDict['finalVars'][ampName] = record['FINAL_VARS']
516 inDict['finalModelVars'][ampName] = record['FINAL_MODEL_VARS']
517 inDict['finalMeans'][ampName] = record['FINAL_MEANS']
518 inDict['badAmps'] = record['BAD_AMPS'].tolist()
519 inDict['photoCharges'][ampName] = record['PHOTO_CHARGE']
520 if calibVersion == 1.0:
521 mask = record['FINAL_MEANS'].mask
522 array = record['FINAL_MEANS'][~mask]
523 if len(array) > 0:
524 inDict['ptcTurnoff'][ampName] = record['FINAL_MEANS'][~mask][-1]
525 else:
526 inDict['ptcTurnoff'][ampName] = np.nan
527 else:
528 inDict['ptcTurnoff'][ampName] = record['PTC_TURNOFF']
529 return cls().fromDict(inDict)
531 def toTable(self):
532 """Construct a list of tables containing the information in this
533 calibration.
535 The list of tables should create an identical calibration
536 after being passed to this class's fromTable method.
538 Returns
539 -------
540 tableList : `list` [`astropy.table.Table`]
541 List of tables containing the linearity calibration
542 information.
543 """
544 tableList = []
545 self.updateMetadata()
547 badAmps = np.array(self.badAmps) if len(self.badAmps) else np.array([], dtype="U3")
549 catalogList = []
550 for ampName in self.ampNames:
551 ampDict = {
552 'AMPLIFIER_NAME': ampName,
553 'PTC_FIT_TYPE': self.ptcFitType,
554 'COV_MATRIX_SIDE': self.covMatrixSide,
555 'INPUT_EXP_ID_PAIRS': self.inputExpIdPairs[ampName],
556 'EXP_ID_MASK': self.expIdMask[ampName],
557 'RAW_EXP_TIMES': self.rawExpTimes[ampName],
558 'RAW_MEANS': self.rawMeans[ampName],
559 'RAW_VARS': self.rawVars[ampName],
560 'GAIN': self.gain[ampName],
561 'GAIN_ERR': self.gainErr[ampName],
562 'NOISE': self.noise[ampName],
563 'NOISE_ERR': self.noiseErr[ampName],
564 'PTC_FIT_PARS': np.array(self.ptcFitPars[ampName]),
565 'PTC_FIT_PARS_ERROR': np.array(self.ptcFitParsError[ampName]),
566 'PTC_FIT_CHI_SQ': self.ptcFitChiSq[ampName],
567 'PTC_TURNOFF': self.ptcTurnoff[ampName],
568 'A_MATRIX': self.aMatrix[ampName].ravel(),
569 'B_MATRIX': self.bMatrix[ampName].ravel(),
570 'A_MATRIX_NO_B': self.aMatrixNoB[ampName].ravel(),
571 'BAD_AMPS': badAmps,
572 'PHOTO_CHARGE': self.photoCharges[ampName],
573 'COVARIANCES': self.covariances[ampName].ravel(),
574 'COVARIANCES_MODEL': self.covariancesModel[ampName].ravel(),
575 'COVARIANCES_SQRT_WEIGHTS': self.covariancesSqrtWeights[ampName].ravel(),
576 'COVARIANCES_MODEL_NO_B': self.covariancesModelNoB[ampName].ravel(),
577 'FINAL_VARS': self.finalVars[ampName],
578 'FINAL_MODEL_VARS': self.finalModelVars[ampName],
579 'FINAL_MEANS': self.finalMeans[ampName],
580 }
581 catalogList.append(ampDict)
583 catalog = Table(catalogList)
585 inMeta = self.getMetadata().toDict()
586 outMeta = {k: v for k, v in inMeta.items() if v is not None}
587 outMeta.update({k: "" for k, v in inMeta.items() if v is None})
588 catalog.meta = outMeta
589 tableList.append(catalog)
591 return(tableList)
593 def fromDetector(self, detector):
594 """Read metadata parameters from a detector.
596 Parameters
597 ----------
598 detector : `lsst.afw.cameraGeom.detector`
599 Input detector with parameters to use.
601 Returns
602 -------
603 calib : `lsst.ip.isr.PhotonTransferCurveDataset`
604 The calibration constructed from the detector.
605 """
607 pass
609 def getExpIdsUsed(self, ampName):
610 """Get the exposures used, i.e. not discarded, for a given amp.
611 If no mask has been created yet, all exposures are returned.
613 Parameters
614 ----------
615 ampName : `str`
617 Returns
618 -------
619 expIdsUsed : `list` [`tuple`]
620 List of pairs of exposure ids used in PTC.
621 """
622 if len(self.expIdMask[ampName]) == 0:
623 return self.inputExpIdPairs[ampName]
625 # if the mask exists it had better be the same length as the expIdPairs
626 assert len(self.expIdMask[ampName]) == len(self.inputExpIdPairs[ampName])
628 pairs = self.inputExpIdPairs[ampName]
629 mask = self.expIdMask[ampName]
630 # cast to bool required because numpy
631 try:
632 expIdsUsed = [(exp1, exp2) for ((exp1, exp2), m) in zip(pairs, mask) if m]
633 except ValueError:
634 warnings.warn("The PTC file was written incorrectly; you should rerun the "
635 "PTC solve task if possible.", RuntimeWarning)
636 expIdsUsed = []
637 for pairList, m in zip(pairs, mask):
638 if m:
639 expIdsUsed.append(pairList[0])
641 return expIdsUsed
643 def getGoodAmps(self):
644 """Get the good amps from this PTC."""
645 return [amp for amp in self.ampNames if amp not in self.badAmps]
647 def getGoodPoints(self, ampName):
648 """Get the good points used for a given amp in the PTC.
650 Parameters
651 ----------
652 ampName : `str`
654 Returns
655 -------
656 goodPoints : `np.ndarray`
657 Boolean array of good points used in PTC.
658 """
659 return self.expIdMask[ampName]