LSST Applications g04e9c324dd+8c5ae1fdc5,g134cb467dc+1b3060144d,g18429d2f64+f642bf4753,g199a45376c+0ba108daf9,g1fd858c14a+2dcf163641,g262e1987ae+7b8c96d2ca,g29ae962dfc+3bd6ecb08a,g2cef7863aa+aef1011c0b,g35bb328faa+8c5ae1fdc5,g3fd5ace14f+53e1a9e7c5,g4595892280+fef73a337f,g47891489e3+2efcf17695,g4d44eb3520+642b70b07e,g53246c7159+8c5ae1fdc5,g67b6fd64d1+2efcf17695,g67fd3c3899+b70e05ef52,g74acd417e5+317eb4c7d4,g786e29fd12+668abc6043,g87389fa792+8856018cbb,g89139ef638+2efcf17695,g8d7436a09f+3be3c13596,g8ea07a8fe4+9f5ccc88ac,g90f42f885a+a4e7b16d9b,g97be763408+ad77d7208f,g9dd6db0277+b70e05ef52,ga681d05dcb+a3f46e7fff,gabf8522325+735880ea63,gac2eed3f23+2efcf17695,gb89ab40317+2efcf17695,gbf99507273+8c5ae1fdc5,gd8ff7fe66e+b70e05ef52,gdab6d2f7ff+317eb4c7d4,gdc713202bf+b70e05ef52,gdfd2d52018+b10e285e0f,ge365c994fd+310e8507c4,ge410e46f29+2efcf17695,geaed405ab2+562b3308c0,gffca2db377+8c5ae1fdc5,w.2025.35
LSST Data Management Base Package
Loading...
Searching...
No Matches
ptcDataset.py
Go to the documentation of this file.
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"""
25
26__all__ = ['PhotonTransferCurveDataset']
27
28import numbers
29import numpy as np
30import math
31from astropy.table import Table
32import numpy.polynomial.polynomial as poly
33from scipy.signal import fftconvolve
34
35from lsst.ip.isr import IsrCalib
36
37
38def symmetrize(inputArray):
39 """ Copy array over 4 quadrants prior to convolution.
40
41 Parameters
42 ----------
43 inputarray : `numpy.array`
44 Input array to symmetrize.
45
46 Returns
47 -------
48 aSym : `numpy.array`
49 Symmetrized array.
50 """
51
52 targetShape = list(inputArray.shape)
53 r1, r2 = inputArray.shape[-1], inputArray.shape[-2]
54 targetShape[-1] = 2*r1-1
55 targetShape[-2] = 2*r2-1
56 aSym = np.ndarray(tuple(targetShape))
57 aSym[..., r2-1:, r1-1:] = inputArray
58 aSym[..., r2-1:, r1-1::-1] = inputArray
59 aSym[..., r2-1::-1, r1-1::-1] = inputArray
60 aSym[..., r2-1::-1, r1-1:] = inputArray
61
62 return aSym
63
64
66 """A simple class to hold the output data from the PTC task.
67
68 The dataset is made up of a dictionary for each item, keyed by the
69 amplifiers' names, which much be supplied at construction time.
70 New items cannot be added to the class to save accidentally saving to the
71 wrong property, and the class can be frozen if desired.
72 inputExpIdPairs records the exposures used to produce the data.
73 When fitPtc() or fitCovariancesAstier() is run, a mask is built up, which
74 is by definition always the same length as inputExpIdPairs, rawExpTimes,
75 rawMeans and rawVars, and is a list of bools, which are incrementally set
76 to False as points are discarded from the fits.
77 PTC fit parameters for polynomials are stored in a list in ascending order
78 of polynomial term, i.e. par[0]*x^0 + par[1]*x + par[2]*x^2 etc
79 with the length of the list corresponding to the order of the polynomial
80 plus one.
81
82 Parameters
83 ----------
84 ampNames : `list`
85 List with the names of the amplifiers of the detector at hand.
86 ptcFitType : `str`, optional
87 Type of model fitted to the PTC: "POLYNOMIAL", "EXPAPPROXIMATION",
88 or "FULLCOVARIANCE".
89 covMatrixSide : `int`, optional
90 Maximum lag of measured covariances (size of square covariance
91 matrices).
92 covMatrixSideFullCovFit : `int`, optional
93 Maximum covariances lag for FULLCOVARIANCE fit. It should be less or
94 equal than covMatrixSide.
95 kwargs : `dict`, optional
96 Other keyword arguments to pass to the parent init.
97
98 Notes
99 -----
100 The stored attributes are:
101
102 badAmps : `list` [`str`]
103 List with bad amplifiers names.
104 inputExpIdPairs : `dict`, [`str`, `list`]
105 Dictionary keyed by amp names containing the input exposures IDs.
106 inputExpPairMjdStartList : `dict`, [`str`, `np.ndarray`]
107 Dictionary keyed by amp names containing the start mjd from
108 the first exposure in each flat pair.
109 expIdMask : `dict`, [`str`, `np.ndarray`]
110 Dictionary keyed by amp names containing the mask produced after
111 outlier rejection. The mask produced by the "FULLCOVARIANCE"
112 option may differ from the one produced in the other two PTC
113 fit types.
114 rawExpTimes : `dict`, [`str`, `np.ndarray`]
115 Dictionary keyed by amp names containing the unmasked exposure times.
116 rawMeans : `dict`, [`str`, `np.ndarray`]
117 Dictionary keyed by amp names containing the unmasked average of the
118 means of the exposures in each flat pair (units: adu).
119 rawVars : `dict`, [`str`, `np.ndarray`]
120 Dictionary keyed by amp names containing the variance of the
121 difference image of the exposures in each flat pair (units: adu^2).
122 rawDeltas : `dict`, [`str`, `np.ndarray`]
123 Dictionary keyed by amp names containing the scaled unmasked delta of
124 the means of the exposures in each flat pair (units: adu).
125 rowMeanVariance : `dict`, [`str`, `np.ndarray`]
126 Dictionary keyed by amp names containing the variance of the
127 means of the rows of the difference image of the exposures
128 in each flat pair (units: adu^2).
129 histVars : `dict`, [`str`, `np.ndarray`]
130 Dictionary keyed by amp names containing the variance of the
131 difference image of the exposures in each flat pair estimated
132 by fitting a Gaussian model.
133 histChi2Dofs : `dict`, [`str`, `np.ndarray`]
134 Dictionary keyed by amp names containing the chi-squared per degree
135 of freedom fitting the difference image to a Gaussian model.
136 kspValues : `dict`, [`str`, `np.ndarray`]
137 Dictionary keyed by amp names containing the KS test p-value from
138 fitting the difference image to a Gaussian model.
139 gain : `dict`, [`str`, `float`]
140 Dictionary keyed by amp names containing the fitted gains. May be
141 adjusted by amp-offset gain ratios if configured in PTC solver.
142 gainUnadjusted : `dict`, [`str`, `float`]
143 Dictionary keyed by amp names containing unadjusted (raw) fit gain
144 values. May be the same as gain values if amp-offset adjustment
145 is not turned on.
146 gainErr : `dict`, [`str`, `float`]
147 Dictionary keyed by amp names containing the errors on the
148 fitted gains.
149 gainList : `dict`, [`str`, `np.ndarray`]
150 Dictionary keyed by amp names containing the gain estimated from
151 each flat pair.
152 overscanMedianLevelList : `dict`, [`str`, `np.ndarray`]
153 Dictionary keyed by amp names containing the median overscan
154 level from each input flat pair (units: adu).
155 overscanMedian : `dict `, [`str`, `float`]
156 Dictionary keyed by amp names containing the median of
157 overscanMedianLevelList[expIdMask] (units: adu).
158 overscanMedianSigma : `dict `, [`str`, `float`]
159 Dictionary keyed by amp names containing the median absolute
160 deviation of overscanMedianLevelList[expIdMask] (units: adu).
161 noiseList : `dict`, [`str`, `np.ndarray`]
162 Dictionary keyed by amp names containing the mean overscan
163 standard deviation from each flat pair (units: adu).
164 noise : `dict`, [`str`, `float`]
165 Dictionary keyed by amp names containing the fitted noise
166 (units: electron).
167 noiseErr : `dict`, [`str`, `float`]
168 Dictionary keyed by amp names containing the errors on the fitted
169 noise (units: electron).
170 ampOffsets : `dict`, [`str`, `float`]
171 Dictionary keyed by amp names containing amp-to-amp offsets
172 (units: adu).
173 ptcFitPars : `dict`, [`str`, `np.ndarray`]
174 Dictionary keyed by amp names containing the fitted parameters of the
175 PTC model for ptcFitType in ["POLYNOMIAL", "EXPAPPROXIMATION"].
176 ptcFitParsError : `dict`, [`str`, `np.ndarray`]
177 Dictionary keyed by amp names containing the errors on the fitted
178 parameters of the PTC model for ptcFitType in
179 ["POLYNOMIAL", "EXPAPPROXIMATION"].
180 ptcFitChiSq : `dict`, [`str`, `float`]
181 Dictionary keyed by amp names containing the reduced chi squared
182 of the fit for ptcFitType in ["POLYNOMIAL", "EXPAPPROXIMATION"].
183 ptcTurnoff : `dict` [`str, `float`]
184 Flux value (in adu) where the variance of the PTC curve starts
185 decreasing consistently.
186 ptcTurnoffSamplingError : `dict` [`str`, `float`]
187 ``Sampling`` error on the ptcTurnoff, based on the flux sampling
188 of the input PTC (units: adu).
189 nPixelCovariances : `dict`, [`str`, `int`]
190 Dictionary keyed by amp names containing the number of pixels
191 that were used to measure the covariances.
192 covariances : `dict`, [`str`, `np.ndarray`]
193 Dictionary keyed by amp names containing a list of measured
194 covariances per mean flux (units: adu^2).
195 covariancesModel : `dict`, [`str`, `np.ndarray`]
196 Dictionary keyed by amp names containinging covariances model
197 (Eq. 20 of Astier+19) per mean flux (units: adu^2).
198 covariancesSqrtWeights : `dict`, [`str`, `np.ndarray`]
199 Dictionary keyed by amp names containinging sqrt. of covariances
200 weights (units: 1/adu).
201 aMatrix : `dict`, [`str`, `np.ndarray`]
202 Dictionary keyed by amp names containing the "a" parameters from
203 the model in Eq. 20 of Astier+19 (units: 1/electron).
204 bMatrix : `dict`, [`str`, `np.ndarray`]
205 Dictionary keyed by amp names containing the "b" parameters from
206 the model in Eq. 20 of Astier+19 (units: 1/electron).
207 noiseMatrix : `dict`, [`str`, `np.ndarray`]
208 Dictionary keyed by amp names containing the "noise" parameters from
209 the model in Eq. 20 of Astier+19 (units: electron^2).
210 covariancesModelNoB : `dict`, [`str`, `np.ndarray`]
211 Dictionary keyed by amp names containing covariances model
212 (with 'b'=0 in Eq. 20 of Astier+19) per mean flux (units:
213 adu^2). Will be removed after v29.
214 aMatrixNoB : `dict`, [`str`, `np.ndarray`]
215 Dictionary keyed by amp names containing the "a" parameters from the
216 model in Eq. 20 of Astier+19 (and 'b' = 0) (units: 1/electron).
217 Will be removed after v29.
218 noiseMatrixNoB : `dict`, [`str`, `np.ndarray`]
219 Dictionary keyed by amp names containing the "noise" parameters from
220 the model in Eq. 20 of Astier+19, with 'b' = 0 (units: electron^2).
221 Will be removed after v29.
222 finalVars : `dict`, [`str`, `np.ndarray`]
223 Dictionary keyed by amp names containing the masked variance of the
224 difference image of each flat
225 pair. If needed, each array will be right-padded with
226 np.nan to match the length of rawExpTimes.
227 finalModelVars : `dict`, [`str`, `np.ndarray`]
228 Dictionary keyed by amp names containing the masked modeled
229 variance of the difference image of each flat pair. If needed, each
230 array will be right-padded with np.nan to match the length of
231 rawExpTimes.
232 finalMeans : `dict`, [`str`, `np.ndarray`]
233 Dictionary keyed by amp names containing the masked average of the
234 means of the exposures in each flat pair. If needed, each array
235 will be right-padded with np.nan to match the length of
236 rawExpTimes.
237 photoCharges : `dict`, [`str`, `np.ndarray`]
238 Dictionary keyed by amp names containing the integrated photocharge
239 for linearity calibration.
240 photoChargeDeltas : `dict`, [`str`, `np.ndarray`]
241 Dictionary keyed by amp names containing the delta for the integrated
242 photocharge.
243 auxValues : `dict`, [`str`, `np.ndarray`]
244 Dictionary of per-detector auxiliary header values that can be used
245 for PTC, linearity computation.
246
247 Version 1.1 adds the `ptcTurnoff` attribute.
248 Version 1.2 adds the `histVars`, `histChi2Dofs`, and `kspValues`
249 attributes.
250 Version 1.3 adds the `noiseMatrix` and `noiseMatrixNoB` attributes.
251 Version 1.4 adds the `auxValues` attribute.
252 Version 1.5 adds the `covMatrixSideFullCovFit` attribute.
253 Version 1.6 adds the `rowMeanVariance` attribute.
254 Version 1.7 adds the `noiseList` attribute.
255 Version 1.8 adds the `ptcTurnoffSamplingError` attribute.
256 Version 1.9 standardizes PTC noise units to electron.
257 Version 2.0 adds the `ampOffsets`, `gainUnadjusted`, and
258 `gainList` attributes.
259 Version 2.1 deprecates the `covariancesModelNoB`, `aMatrixNoB`, and
260 `noiseMatrixNoB` attributes.
261 Version 2.2 adds the `overscanMedianLevelList` and
262 `inputExpPairMjdStartList` attributes.
263 Version 2.3 adds the `overscanMedian` and
264 `overscanMedianSigma` attrbutes.
265 Version 2.4 adds the `nPixelCovariances` attribute.
266 Version 2.5 adds the `rawDeltas` and `photoChargeDeltas` attributes.
267 """
268
269 _OBSTYPE = 'PTC'
270 _SCHEMA = 'Gen3 Photon Transfer Curve'
271 # When adding a new field to update the version, be sure to update the
272 # following methods:
273 # * __init__()
274 # * fromDict()
275 # * toDict()
276 # * fromTable()
277 # * toTable()
278 # * setAmpValuesPartialDataset()
279 # * appendPartialPtc()
280 # * sort()
281 # * _checkTypes() in test_ptcDataset.py
282 # * test_ptcDataset() in test_ptcDataset.py
283 # * test_ptcDatasetSort in test_ptcDataset.py
284 # * test_ptcDatasetAppend in test_ptcDataset.py
285 _VERSION = 2.5
286
287 def __init__(self, ampNames=[], ptcFitType=None, covMatrixSide=1,
288 covMatrixSideFullCovFit=None, **kwargs):
289 self.ptcFitType = ptcFitType
290 self.ampNames = ampNames
291 self.covMatrixSide = covMatrixSide
292 if covMatrixSideFullCovFit is None:
293 self.covMatrixSideFullCovFit = covMatrixSide
294 else:
295 self.covMatrixSideFullCovFit = covMatrixSideFullCovFit
296
297 self.badAmps = []
298
299 self.inputExpIdPairs = {ampName: [] for ampName in ampNames}
300 self.inputExpPairMjdStartList = {ampName: np.array([]) for ampName in ampNames}
301 self.expIdMask = {ampName: np.array([], dtype=bool) for ampName in ampNames}
302 self.rawExpTimes = {ampName: np.array([]) for ampName in ampNames}
303 self.rawMeans = {ampName: np.array([]) for ampName in ampNames}
304 self.rawVars = {ampName: np.array([]) for ampName in ampNames}
305 self.rawDeltas = {ampName: np.array([]) for ampName in ampNames}
306 self.rowMeanVariance = {ampName: np.array([]) for ampName in ampNames}
307 self.photoCharges = {ampName: np.array([]) for ampName in ampNames}
308 self.photoChargeDeltas = {ampName: np.array([]) for ampName in ampNames}
309 self.ampOffsets = {ampName: np.array([]) for ampName in ampNames}
310
311 self.gain = {ampName: np.nan for ampName in ampNames}
312 self.gainUnadjusted = {ampName: np.nan for ampName in ampNames}
313 self.gainErr = {ampName: np.nan for ampName in ampNames}
314 self.gainList = {ampName: np.array([]) for ampName in ampNames}
315 self.overscanMedianLevelList = {ampName: np.array([]) for ampName in ampNames}
316 self.overscanMedian = {ampName: np.nan for ampName in ampNames}
317 self.overscanMedianSigma = {ampName: np.nan for ampName in ampNames}
318 self.noiseList = {ampName: np.array([]) for ampName in ampNames}
319 self.noise = {ampName: np.nan for ampName in ampNames}
320 self.noiseErr = {ampName: np.nan for ampName in ampNames}
321
322 self.histVars = {ampName: np.array([]) for ampName in ampNames}
323 self.histChi2Dofs = {ampName: np.array([]) for ampName in ampNames}
324 self.kspValues = {ampName: np.array([]) for ampName in ampNames}
325
326 self.ptcFitPars = {ampName: np.array([]) for ampName in ampNames}
327 self.ptcFitParsError = {ampName: np.array([]) for ampName in ampNames}
328 self.ptcFitChiSq = {ampName: np.nan for ampName in ampNames}
329 self.ptcTurnoff = {ampName: np.nan for ampName in ampNames}
330 self.ptcTurnoffSamplingError = {ampName: np.nan for ampName in ampNames}
331
332 self.nPixelCovariances = {ampName: -1 for ampName in ampNames}
333 self.covariances = {ampName: np.array([]) for ampName in ampNames}
334 self.covariancesModel = {ampName: np.array([]) for ampName in ampNames}
335 self.covariancesSqrtWeights = {ampName: np.array([]) for ampName in ampNames}
336 self.aMatrix = {ampName: np.array([]) for ampName in ampNames}
337 self.bMatrix = {ampName: np.array([]) for ampName in ampNames}
338 self.noiseMatrix = {ampName: np.array([]) for ampName in ampNames}
339
340 self.finalVars = {ampName: np.array([]) for ampName in ampNames}
341 self.finalModelVars = {ampName: np.array([]) for ampName in ampNames}
342 self.finalMeans = {ampName: np.array([]) for ampName in ampNames}
343
344 # Try this as a dict of arrays.
345 self.auxValues = {}
346
347 super().__init__(**kwargs)
348 self.requiredAttributes.update(['badAmps', 'inputExpIdPairs', 'inputExpPairMjdStartList',
349 'expIdMask', 'rawExpTimes', 'rawMeans', 'rawVars',
350 'rowMeanVariance', 'gain', 'gainErr', 'gainList', 'noise',
351 'noiseErr', 'noiseList', 'overscanMedianLevelList',
352 'overscanMedian', 'overscanMedianSigma', 'ptcFitPars',
353 'ptcFitParsError', 'ptcFitChiSq', 'ptcTurnoff', 'covariances',
354 'covariancesModel', 'covariancesSqrtWeights', 'aMatrix',
355 'bMatrix', 'noiseMatrix', 'finalVars', 'finalModelVars',
356 'finalMeans', 'photoCharges', 'histVars', 'histChi2Dofs',
357 'kspValues', 'auxValues', 'ptcTurnoffSamplingError',
358 'ampOffsets', 'gainUnadjusted', 'nPixelCovariances',
359 'rawDeltas', 'photoChargeDeltas'])
360
361 self.updateMetadata(setCalibInfo=True, setCalibId=True, **kwargs)
363
365 self,
366 ampName,
367 inputExpIdPair=(-1, -1),
368 inputExpPairMjdStart=np.nan,
369 rawExpTime=np.nan,
370 rawMean=np.nan,
371 rawVar=np.nan,
372 rawDelta=np.nan,
373 rowMeanVariance=np.nan,
374 photoCharge=np.nan,
375 photoChargeDelta=np.nan,
376 ampOffset=np.nan,
377 expIdMask=False,
378 nPixelCovariance=-1,
379 covariance=None,
380 covSqrtWeights=None,
381 gain=np.nan,
382 noise=np.nan,
383 overscanMedianLevel=np.nan,
384 histVar=np.nan,
385 histChi2Dof=np.nan,
386 kspValue=0.0,
387 ):
388 """
389 Set the amp values for a partial PTC Dataset (from cpExtractPtcTask).
390
391 Parameters
392 ----------
393 ampName : `str`
394 Name of the amp to set the values.
395 inputExpIdPair : `tuple` [`int`]
396 Exposure IDs of input pair.
397 inputExpPairMjdStart : `float`, optional
398 The start MJD of first exposure in the flat pair.
399 rawExpTime : `float`, optional
400 Exposure time for this exposure pair (units: sec).
401 rawMean : `float`, optional
402 Average of the means of the exposures in this pair
403 (units: adu).
404 rawVar : `float`, optional
405 Variance of the difference of the exposures in this pair
406 (units: adu^2).
407 rawDelta : `float`, optional
408 Delta of the means of the exposure in this pair
409 (units: adu).
410 rowMeanVariance : `float`, optional
411 Variance of the means of the rows in the difference image
412 of the exposures in this pair (units: adu^2).
413 photoCharge : `float`, optional
414 Integrated photocharge for flat pair for linearity calibration
415 (arbitrary units).
416 photoChargeDelta : `float`, optional
417 Delta between integrated photocharge for the flat pair
418 (arbitrary units).
419 ampOffset : `float`, optional
420 Amp offset for this amplifier.
421 expIdMask : `bool`, optional
422 Flag setting if this exposure pair should be used (True)
423 or not used (False).
424 nPixelCovariance : `int`, optional
425 Number of pixels that went into the covariance measurement.
426 covariance : `np.ndarray` or None, optional
427 Measured covariance for this exposure pair (units: adu^2).
428 covSqrtWeights : `np.ndarray` or None, optional
429 Measured sqrt of covariance weights in this exposure pair
430 (units: 1/adu).
431 gain : `float`, optional
432 Estimated gain for this exposure pair (units: electron/adu).
433 noise : `float`, optional
434 Estimated read noise for this exposure pair (units: electron).
435 overscanMedianLevel : `float`, optional
436 Average of the median overscan levels for this exposure pair.
437 (units: adu)
438 histVar : `float`, optional
439 Variance estimated from fitting a histogram with a Gaussian model
440 (units: adu).
441 histChi2Dof : `float`, optional
442 Chi-squared per degree of freedom from Gaussian histogram fit.
443 kspValue : `float`, optional
444 KS test p-value from the Gaussian histogram fit.
445 """
446 nanMatrix = np.full((self.covMatrixSide, self.covMatrixSide), np.nan)
447 nanMatrixFit = np.full((self.covMatrixSideFullCovFit,
448 self.covMatrixSideFullCovFit), np.nan)
449 if covariance is None:
450 covariance = nanMatrix
451 if covSqrtWeights is None:
452 covSqrtWeights = nanMatrix
453
454 self.inputExpIdPairs[ampName] = [inputExpIdPair]
455 self.inputExpPairMjdStartList[ampName] = np.array([inputExpPairMjdStart])
456 self.rawExpTimes[ampName] = np.array([rawExpTime])
457 self.rawMeans[ampName] = np.array([rawMean])
458 self.rawVars[ampName] = np.array([rawVar])
459 self.rawDeltas[ampName] = np.array([rawDelta])
460 self.rowMeanVariance[ampName] = np.array([rowMeanVariance])
461 self.photoCharges[ampName] = np.array([photoCharge])
462 self.photoChargeDeltas[ampName] = np.array([photoChargeDelta])
463 self.ampOffsets[ampName] = np.array([ampOffset])
464 self.expIdMask[ampName] = np.array([expIdMask])
465 self.nPixelCovariances[ampName] = nPixelCovariance
466 self.covariances[ampName] = np.array([covariance])
467 self.covariancesSqrtWeights[ampName] = np.array([covSqrtWeights])
468 self.gain[ampName] = gain
469 self.gainUnadjusted[ampName] = gain
470 self.gainList[ampName] = np.array([gain])
471 self.noise[ampName] = noise
472 self.noiseList[ampName] = np.array([noise])
473 self.overscanMedianLevelList[ampName] = np.array([overscanMedianLevel])
474 self.overscanMedian[ampName] = float(overscanMedianLevel)
475 self.overscanMedianSigma[ampName] = float(0.0)
476 self.histVars[ampName] = np.array([histVar])
477 self.histChi2Dofs[ampName] = np.array([histChi2Dof])
478 self.kspValues[ampName] = np.array([kspValue])
479
480 # From FULLCOVARIANCE model
481 self.covariancesModel[ampName] = np.array([nanMatrixFit])
482 self.aMatrix[ampName] = nanMatrixFit
483 self.bMatrix[ampName] = nanMatrixFit
484 self.noiseMatrix[ampName] = nanMatrixFit
485 # Filler values.
486 self.finalVars[ampName] = np.array([np.nan])
487 self.finalModelVars[ampName] = np.array([np.nan])
488 self.finalMeans[ampName] = np.array([np.nan])
489
490 def setAuxValuesPartialDataset(self, auxDict):
491 """
492 Set a dictionary of auxiliary values for a partial dataset.
493
494 Parameters
495 ----------
496 auxDict : `dict` [`str`, `float`]
497 Dictionary of float values.
498 """
499 for key, value in auxDict.items():
500 if isinstance(value, numbers.Integral):
501 self.auxValues[key] = np.atleast_1d(np.asarray(value).astype(np.int64))
502 elif isinstance(value, (str, np.str_, np.bytes_)):
503 self.auxValues[key] = np.atleast_1d(np.asarray(value))
504 else:
505 self.auxValues[key] = np.atleast_1d(np.array(value, dtype=np.float64))
506
507 def updateMetadata(self, **kwargs):
508 """Update calibration metadata.
509 This calls the base class's method after ensuring the required
510 calibration keywords will be saved.
511
512 Parameters
513 ----------
514 setDate : `bool`, optional
515 Update the CALIBDATE fields in the metadata to the current
516 time. Defaults to False.
517 kwargs :
518 Other keyword parameters to set in the metadata.
519 """
520 super().updateMetadata(PTC_FIT_TYPE=self.ptcFitType, **kwargs)
521
522 @classmethod
523 def fromDict(cls, dictionary):
524 """Construct a calibration from a dictionary of properties.
525 Must be implemented by the specific calibration subclasses.
526
527 Parameters
528 ----------
529 dictionary : `dict`
530 Dictionary of properties.
531
532 Returns
533 -------
534 calib : `lsst.ip.isr.PhotonTransferCurveDataset`
535 Constructed calibration.
536
537 Raises
538 ------
539 RuntimeError
540 Raised if the supplied dictionary is for a different
541 calibration.
542 """
543 calib = cls()
544 if calib._OBSTYPE != dictionary['metadata']['OBSTYPE']:
545 raise RuntimeError(f"Incorrect Photon Transfer Curve dataset supplied. "
546 f"Expected {calib._OBSTYPE}, found {dictionary['metadata']['OBSTYPE']}")
547 calib.setMetadata(dictionary['metadata'])
548 calib.ptcFitType = dictionary['ptcFitType']
549 calib.covMatrixSide = dictionary['covMatrixSide']
550 calib.covMatrixSideFullCovFit = dictionary['covMatrixSideFullCovFit']
551 calib.badAmps = np.array(dictionary['badAmps'], 'str').tolist()
552 calib.ampNames = []
553
554 # The cov matrices are square
555 covMatrixSide = calib.covMatrixSide
556 covMatrixSideFullCovFit = calib.covMatrixSideFullCovFit
557 # Number of final signal levels
558 covDimensionsProduct = len(np.array(list(dictionary['covariances'].values())[0]).ravel())
559 nSignalPoints = int(covDimensionsProduct/(covMatrixSide*covMatrixSide))
560
561 for ampName in dictionary['ampNames']:
562 calib.ampNames.append(ampName)
563 calib.inputExpIdPairs[ampName] = dictionary['inputExpIdPairs'][ampName]
564 calib.inputExpPairMjdStartList[ampName] = np.array(
565 dictionary['inputExpPairMjdStartList'][ampName],
566 )
567 calib.expIdMask[ampName] = np.array(dictionary['expIdMask'][ampName])
568 calib.rawExpTimes[ampName] = np.array(dictionary['rawExpTimes'][ampName], dtype=np.float64)
569 calib.rawMeans[ampName] = np.array(dictionary['rawMeans'][ampName], dtype=np.float64)
570 calib.rawVars[ampName] = np.array(dictionary['rawVars'][ampName], dtype=np.float64)
571 calib.rawDeltas[ampName] = np.array(dictionary['rawDeltas'][ampName], dtype=np.float64)
572 calib.rowMeanVariance[ampName] = np.array(dictionary['rowMeanVariance'][ampName],
573 dtype=np.float64)
574 calib.gain[ampName] = float(dictionary['gain'][ampName])
575 calib.gainErr[ampName] = float(dictionary['gainErr'][ampName])
576 calib.gainUnadjusted[ampName] = float(dictionary['gainUnadjusted'][ampName])
577 calib.gainList[ampName] = np.array(dictionary['gainList'][ampName], dtype=np.float64)
578 calib.noiseList[ampName] = np.array(dictionary['noiseList'][ampName], dtype=np.float64)
579 calib.nPixelCovariances[ampName] = int(dictionary['nPixelCovariances'][ampName])
580 calib.overscanMedianLevelList[ampName] = np.array(
581 dictionary['overscanMedianLevelList'][ampName],
582 dtype=np.float64,
583 )
584 calib.overscanMedian[ampName] = float(dictionary['overscanMedian'][ampName])
585 calib.overscanMedianSigma[ampName] = float(dictionary['overscanMedianSigma'][ampName])
586 calib.noise[ampName] = float(dictionary['noise'][ampName])
587 calib.noiseErr[ampName] = float(dictionary['noiseErr'][ampName])
588 calib.histVars[ampName] = np.array(dictionary['histVars'][ampName], dtype=np.float64)
589 calib.histChi2Dofs[ampName] = np.array(dictionary['histChi2Dofs'][ampName], dtype=np.float64)
590 calib.kspValues[ampName] = np.array(dictionary['kspValues'][ampName], dtype=np.float64)
591 calib.ptcFitPars[ampName] = np.array(dictionary['ptcFitPars'][ampName], dtype=np.float64)
592 calib.ptcFitParsError[ampName] = np.array(dictionary['ptcFitParsError'][ampName],
593 dtype=np.float64)
594 calib.ptcFitChiSq[ampName] = float(dictionary['ptcFitChiSq'][ampName])
595 calib.ptcTurnoff[ampName] = float(dictionary['ptcTurnoff'][ampName])
596 calib.ptcTurnoffSamplingError[ampName] = float(dictionary['ptcTurnoffSamplingError'][ampName])
597 if nSignalPoints > 0:
598 # Regular dataset
599 calib.covariances[ampName] = np.array(dictionary['covariances'][ampName],
600 dtype=np.float64).reshape(
601 (nSignalPoints, covMatrixSide, covMatrixSide))
602 calib.covariancesModel[ampName] = np.array(
603 dictionary['covariancesModel'][ampName],
604 dtype=np.float64).reshape(
605 (nSignalPoints, covMatrixSideFullCovFit, covMatrixSideFullCovFit))
606 calib.covariancesSqrtWeights[ampName] = np.array(
607 dictionary['covariancesSqrtWeights'][ampName],
608 dtype=np.float64).reshape(
609 (nSignalPoints, covMatrixSide, covMatrixSide))
610 calib.aMatrix[ampName] = np.array(dictionary['aMatrix'][ampName],
611 dtype=np.float64).reshape(
612 (covMatrixSideFullCovFit, covMatrixSideFullCovFit))
613 calib.bMatrix[ampName] = np.array(dictionary['bMatrix'][ampName],
614 dtype=np.float64).reshape(
615 (covMatrixSideFullCovFit, covMatrixSideFullCovFit))
616 calib.noiseMatrix[ampName] = np.array(
617 dictionary['noiseMatrix'][ampName],
618 dtype=np.float64).reshape((covMatrixSideFullCovFit, covMatrixSideFullCovFit))
619 else:
620 # Empty dataset
621 calib.covariances[ampName] = np.array([], dtype=np.float64)
622 calib.covariancesModel[ampName] = np.array([], dtype=np.float64)
623 calib.covariancesSqrtWeights[ampName] = np.array([], dtype=np.float64)
624 calib.aMatrix[ampName] = np.array([], dtype=np.float64)
625 calib.bMatrix[ampName] = np.array([], dtype=np.float64)
626 calib.noiseMatrix[ampName] = np.array([], dtype=np.float64)
627
628 calib.finalVars[ampName] = np.array(dictionary['finalVars'][ampName], dtype=np.float64)
629 calib.finalModelVars[ampName] = np.array(dictionary['finalModelVars'][ampName], dtype=np.float64)
630 calib.finalMeans[ampName] = np.array(dictionary['finalMeans'][ampName], dtype=np.float64)
631 calib.photoCharges[ampName] = np.array(dictionary['photoCharges'][ampName], dtype=np.float64)
632 calib.photoChargeDeltas[ampName] = np.array(
633 dictionary['photoChargeDeltas'][ampName],
634 dtype=np.float64,
635 )
636 calib.ampOffsets[ampName] = np.array(dictionary['ampOffsets'][ampName], dtype=np.float64)
637
638 for key, value in dictionary['auxValues'].items():
639 if isinstance(value[0], numbers.Integral):
640 calib.auxValues[key] = np.atleast_1d(np.asarray(value).astype(np.int64))
641 elif isinstance(value[0], (str, np.str_, np.bytes_)):
642 calib.auxValues[key] = np.atleast_1d(np.asarray(value))
643 else:
644 calib.auxValues[key] = np.atleast_1d(np.array(value, dtype=np.float64))
645
646 calib.updateMetadata()
647 return calib
648
649 def toDict(self):
650 """Return a dictionary containing the calibration properties.
651 The dictionary should be able to be round-tripped through
652 `fromDict`.
653
654 Returns
655 -------
656 dictionary : `dict`
657 Dictionary of properties.
658 """
659 self.updateMetadata()
660
661 outDict = dict()
662 metadata = self.getMetadata()
663 outDict['metadata'] = metadata
664
665 def _dictOfArraysToDictOfLists(dictOfArrays):
666 dictOfLists = {}
667 for key, value in dictOfArrays.items():
668 dictOfLists[key] = value.ravel().tolist()
669
670 return dictOfLists
671
672 outDict['ptcFitType'] = self.ptcFitType
673 outDict['covMatrixSide'] = self.covMatrixSide
674 outDict['covMatrixSideFullCovFit'] = self.covMatrixSideFullCovFit
675 outDict['ampNames'] = self.ampNames
676 outDict['badAmps'] = self.badAmps
677 outDict['inputExpIdPairs'] = self.inputExpIdPairs
678 outDict['inputExpPairMjdStartList'] = _dictOfArraysToDictOfLists(self.inputExpPairMjdStartList)
679 outDict['expIdMask'] = _dictOfArraysToDictOfLists(self.expIdMask)
680 outDict['rawExpTimes'] = _dictOfArraysToDictOfLists(self.rawExpTimes)
681 outDict['rawMeans'] = _dictOfArraysToDictOfLists(self.rawMeans)
682 outDict['rawVars'] = _dictOfArraysToDictOfLists(self.rawVars)
683 outDict['rawDeltas'] = _dictOfArraysToDictOfLists(self.rawDeltas)
684 outDict['rowMeanVariance'] = _dictOfArraysToDictOfLists(self.rowMeanVariance)
685 outDict['gain'] = self.gain
686 outDict['gainErr'] = self.gainErr
687 outDict['gainUnadjusted'] = self.gainUnadjusted
688 outDict['gainList'] = _dictOfArraysToDictOfLists(self.gainList)
689 outDict['noiseList'] = _dictOfArraysToDictOfLists(self.noiseList)
690 outDict['overscanMedianLevelList'] = _dictOfArraysToDictOfLists(self.overscanMedianLevelList)
691 outDict['overscanMedian'] = self.overscanMedian
692 outDict['overscanMedianSigma'] = self.overscanMedianSigma
693 outDict['noise'] = self.noise
694 outDict['noiseErr'] = self.noiseErr
695 outDict['histVars'] = self.histVars
696 outDict['histChi2Dofs'] = self.histChi2Dofs
697 outDict['kspValues'] = self.kspValues
698 outDict['ptcFitPars'] = _dictOfArraysToDictOfLists(self.ptcFitPars)
699 outDict['ptcFitParsError'] = _dictOfArraysToDictOfLists(self.ptcFitParsError)
700 outDict['ptcFitChiSq'] = self.ptcFitChiSq
701 outDict['ptcTurnoff'] = self.ptcTurnoff
702 outDict['ptcTurnoffSamplingError'] = self.ptcTurnoffSamplingError
703 outDict['nPixelCovariances'] = self.nPixelCovariances
704 outDict['covariances'] = _dictOfArraysToDictOfLists(self.covariances)
705 outDict['covariancesModel'] = _dictOfArraysToDictOfLists(self.covariancesModel)
706 outDict['covariancesSqrtWeights'] = _dictOfArraysToDictOfLists(self.covariancesSqrtWeights)
707 outDict['aMatrix'] = _dictOfArraysToDictOfLists(self.aMatrix)
708 outDict['bMatrix'] = _dictOfArraysToDictOfLists(self.bMatrix)
709 outDict['noiseMatrix'] = _dictOfArraysToDictOfLists(self.noiseMatrix)
710 outDict['finalVars'] = _dictOfArraysToDictOfLists(self.finalVars)
711 outDict['finalModelVars'] = _dictOfArraysToDictOfLists(self.finalModelVars)
712 outDict['finalMeans'] = _dictOfArraysToDictOfLists(self.finalMeans)
713 outDict['photoCharges'] = _dictOfArraysToDictOfLists(self.photoCharges)
714 outDict['photoChargeDeltas'] = _dictOfArraysToDictOfLists(self.photoChargeDeltas)
715 outDict['ampOffsets'] = _dictOfArraysToDictOfLists(self.ampOffsets)
716 outDict['auxValues'] = _dictOfArraysToDictOfLists(self.auxValues)
717
718 return outDict
719
720 @classmethod
721 def fromTable(cls, tableList):
722 """Construct calibration from a list of tables.
723 This method uses the `fromDict` method to create the
724 calibration, after constructing an appropriate dictionary from
725 the input tables.
726
727 Parameters
728 ----------
729 tableList : `list` [`lsst.afw.table.Table`]
730 List of tables to use to construct the datasetPtc.
731
732 Returns
733 -------
734 calib : `lsst.ip.isr.PhotonTransferCurveDataset`
735 The calibration defined in the tables.
736 """
737 ptcTable = tableList[0]
738
739 metadata = ptcTable.meta
740 inDict = dict()
741 inDict['metadata'] = metadata
742 inDict['ampNames'] = []
743 inDict['ptcFitType'] = []
744 inDict['covMatrixSide'] = []
745 inDict['covMatrixSideFullCovFit'] = []
746 inDict['inputExpIdPairs'] = dict()
747 inDict['inputExpPairMjdStartList'] = dict()
748 inDict['expIdMask'] = dict()
749 inDict['rawExpTimes'] = dict()
750 inDict['rawMeans'] = dict()
751 inDict['rawVars'] = dict()
752 inDict['rawDeltas'] = dict()
753 inDict['rowMeanVariance'] = dict()
754 inDict['gain'] = dict()
755 inDict['gainErr'] = dict()
756 inDict['gainUnadjusted'] = dict()
757 inDict['gainList'] = dict()
758 inDict['noiseList'] = dict()
759 inDict['overscanMedianLevelList'] = dict()
760 inDict['overscanMedian'] = dict()
761 inDict['overscanMedianSigma'] = dict()
762 inDict['noise'] = dict()
763 inDict['noiseErr'] = dict()
764 inDict['histVars'] = dict()
765 inDict['histChi2Dofs'] = dict()
766 inDict['kspValues'] = dict()
767 inDict['ptcFitPars'] = dict()
768 inDict['ptcFitParsError'] = dict()
769 inDict['ptcFitChiSq'] = dict()
770 inDict['ptcTurnoff'] = dict()
771 inDict['ptcTurnoffSamplingError'] = dict()
772 inDict['nPixelCovariances'] = dict()
773 inDict['covariances'] = dict()
774 inDict['covariancesModel'] = dict()
775 inDict['covariancesSqrtWeights'] = dict()
776 inDict['aMatrix'] = dict()
777 inDict['bMatrix'] = dict()
778 inDict['noiseMatrix'] = dict()
779 inDict['finalVars'] = dict()
780 inDict['finalModelVars'] = dict()
781 inDict['finalMeans'] = dict()
782 inDict['badAmps'] = []
783 inDict['photoCharges'] = dict()
784 inDict['photoChargeDeltas'] = dict()
785 inDict['ampOffsets'] = dict()
786
787 # TODO: DM-47610, remove after v29
788 inDict['noiseMatrixNoB'] = dict()
789 inDict['covariancesModelNoB'] = dict()
790 inDict['aMatrixNoB'] = dict()
791
792 calibVersion = metadata['PTC_VERSION']
793 if calibVersion == 1.0:
794 cls().log.warning(f"Previous version found for PTC dataset: {calibVersion}. "
795 f"Setting 'ptcTurnoff' in all amps to last value in 'finalMeans'.")
796 for record in ptcTable:
797 ampName = record['AMPLIFIER_NAME']
798
799 inDict['ptcFitType'] = record['PTC_FIT_TYPE']
800 inDict['covMatrixSide'] = record['COV_MATRIX_SIDE']
801 inDict['ampNames'].append(ampName)
802 inDict['inputExpIdPairs'][ampName] = record['INPUT_EXP_ID_PAIRS'].tolist()
803 inDict['expIdMask'][ampName] = record['EXP_ID_MASK']
804 inDict['rawExpTimes'][ampName] = record['RAW_EXP_TIMES']
805 inDict['rawMeans'][ampName] = record['RAW_MEANS']
806 inDict['rawVars'][ampName] = record['RAW_VARS']
807 inDict['gain'][ampName] = record['GAIN']
808 inDict['gainErr'][ampName] = record['GAIN_ERR']
809 inDict['noise'][ampName] = record['NOISE']
810 inDict['noiseErr'][ampName] = record['NOISE_ERR']
811 inDict['ptcFitPars'][ampName] = record['PTC_FIT_PARS']
812 inDict['ptcFitParsError'][ampName] = record['PTC_FIT_PARS_ERROR']
813 inDict['ptcFitChiSq'][ampName] = record['PTC_FIT_CHI_SQ']
814 inDict['covariances'][ampName] = record['COVARIANCES']
815 inDict['covariancesModel'][ampName] = record['COVARIANCES_MODEL']
816 inDict['covariancesSqrtWeights'][ampName] = record['COVARIANCES_SQRT_WEIGHTS']
817 inDict['aMatrix'][ampName] = record['A_MATRIX']
818 inDict['bMatrix'][ampName] = record['B_MATRIX']
819 inDict['finalVars'][ampName] = record['FINAL_VARS']
820 inDict['finalModelVars'][ampName] = record['FINAL_MODEL_VARS']
821 inDict['finalMeans'][ampName] = record['FINAL_MEANS']
822 inDict['badAmps'] = record['BAD_AMPS'].tolist()
823 inDict['photoCharges'][ampName] = record['PHOTO_CHARGE']
824 if calibVersion == 1.0:
825 mask = record['FINAL_MEANS'].mask
826 array = record['FINAL_MEANS'][~mask]
827 if len(array) > 0:
828 inDict['ptcTurnoff'][ampName] = record['FINAL_MEANS'][~mask][-1]
829 else:
830 inDict['ptcTurnoff'][ampName] = np.nan
831 else:
832 inDict['ptcTurnoff'][ampName] = record['PTC_TURNOFF']
833 if calibVersion < 1.2:
834 inDict['histVars'][ampName] = np.array([np.nan])
835 inDict['histChi2Dofs'][ampName] = np.array([np.nan])
836 inDict['kspValues'][ampName] = np.array([0.0])
837 else:
838 inDict['histVars'][ampName] = record['HIST_VARS']
839 inDict['histChi2Dofs'][ampName] = record['HIST_CHI2_DOFS']
840 inDict['kspValues'][ampName] = record['KS_PVALUES']
841 if calibVersion < 1.3:
842 nanMatrix = np.full_like(inDict['aMatrix'][ampName], np.nan)
843 inDict['noiseMatrix'][ampName] = nanMatrix
844 inDict['noiseMatrixNoB'][ampName] = nanMatrix
845 elif calibVersion >= 1.3 and calibVersion < 2.1:
846 inDict['noiseMatrix'][ampName] = record['NOISE_MATRIX']
847 inDict['noiseMatrixNoB'][ampName] = record['NOISE_MATRIX_NO_B']
848 else:
849 inDict['noiseMatrix'][ampName] = record['NOISE_MATRIX']
850 nanMatrix = np.full_like(inDict['aMatrix'][ampName], np.nan)
851 inDict['noiseMatrixNoB'][ampName] = nanMatrix
852 if calibVersion < 1.5:
853 # Matched to `COV_MATRIX_SIDE`. Same for all amps.
854 inDict['covMatrixSideFullCovFit'] = inDict['covMatrixSide']
855 else:
856 inDict['covMatrixSideFullCovFit'] = record['COV_MATRIX_SIDE_FULL_COV_FIT']
857 if calibVersion < 1.6:
858 inDict['rowMeanVariance'][ampName] = np.full((len(inDict['expIdMask'][ampName]),), np.nan)
859 else:
860 inDict['rowMeanVariance'][ampName] = record['ROW_MEAN_VARIANCE']
861 if calibVersion < 1.7:
862 inDict['noiseList'][ampName] = np.full_like(inDict['rawMeans'][ampName], np.nan)
863 else:
864 inDict['noiseList'][ampName] = record['NOISE_LIST']
865 if calibVersion < 1.8:
866 inDict['ptcTurnoffSamplingError'][ampName] = np.nan
867 else:
868 inDict['ptcTurnoffSamplingError'][ampName] = record['PTC_TURNOFF_SAMPLING_ERROR']
869 if calibVersion < 1.9 and inDict['ptcFitType'] == "FULLCOVARIANCE":
870 # Before version 1.9, the noise stored in the PTC was in
871 # units of electron^2 only if ptcFitType == FULLCOVARIANCE.
872 # After version 1.9, we standardized the
873 # PhotonTransferCurveDataset noise units to electron to fix
874 # this bug. If a user tries to use an earlier version of
875 # PTC with this fit type, we must be sure to do the
876 # calculations properly. More information about this noise
877 # issue can be found in DM-45976.
878 if ampName == inDict['ampNames'][0]:
879 cls().log.info(f"Input PTC VERSION ({calibVersion}) < 1.9 and"
880 " ptcFitType == FULLCOVARIANCE. Applying fix for"
881 f" the DM-45976 noise issue.")
882 # The noiseErr calculation was accidentally correct in the
883 # previous version, so we only need to upday the noise
884 # attribute.
885 inDict['noise'][ampName] = np.sqrt(record['noise'][ampName])
886 if calibVersion < 2.0:
887 inDict['ampOffsets'][ampName] = np.full_like(inDict['rawMeans'][ampName], np.nan)
888 inDict['gainUnadjusted'][ampName] = record['GAIN']
889 inDict['gainList'][ampName] = np.full_like(inDict['rawMeans'][ampName], np.nan)
890 else:
891 inDict['ampOffsets'][ampName] = record['AMP_OFFSETS']
892 inDict['gainUnadjusted'][ampName] = record['GAIN_UNADJUSTED']
893 inDict['gainList'][ampName] = record['GAIN_LIST']
894 if calibVersion < 2.1:
895 inDict['covariancesModelNoB'][ampName] = record['COVARIANCES_MODEL_NO_B']
896 inDict['aMatrixNoB'][ampName] = record['A_MATRIX_NO_B']
897 else:
898 nanMatrixList = np.full_like(inDict['covariances'][ampName], np.nan)
899 inDict['covariancesModelNoB'][ampName] = nanMatrixList
900 nanMatrix = np.full_like(inDict['aMatrix'][ampName], np.nan)
901 inDict['aMatrixNoB'][ampName] = nanMatrix
902 if calibVersion < 2.2:
903 inDict['inputExpPairMjdStartList'][ampName] = np.full_like(
904 inDict['rawMeans'][ampName],
905 np.nan,
906 )
907 inDict['overscanMedianLevelList'][ampName] = np.full_like(
908 inDict['rawMeans'][ampName],
909 np.nan,
910 )
911 else:
912 inDict['inputExpPairMjdStartList'][ampName] = record['INPUT_EXP_PAIR_MJD_START']
913 inDict['overscanMedianLevelList'][ampName] = record['OVERSCAN_MEDIAN_LIST']
914 if calibVersion < 2.3:
915 inDict['overscanMedian'][ampName] = np.nan
916 inDict['overscanMedianSigma'][ampName] = np.nan
917 else:
918 inDict['overscanMedian'][ampName] = record['OVERSCAN_MEDIAN']
919 inDict['overscanMedianSigma'][ampName] = record['OVERSCAN_MEDIAN_SIGMA']
920 if calibVersion < 2.4:
921 inDict['nPixelCovariances'][ampName] = -1
922 else:
923 inDict['nPixelCovariances'][ampName] = record['NPIXEL_COVARIANCES']
924 if calibVersion < 2.5:
925 inDict['rawDeltas'][ampName] = np.full_like(
926 inDict['rawMeans'][ampName],
927 np.nan,
928 )
929 inDict['photoChargeDeltas'][ampName] = np.full_like(
930 inDict['rawMeans'][ampName],
931 np.nan,
932 )
933 else:
934 inDict['rawDeltas'][ampName] = record['RAW_DELTAS']
935 inDict['photoChargeDeltas'][ampName] = record['PHOTO_CHARGE_DELTAS']
936
937 inDict['auxValues'] = {}
938 record = ptcTable[0]
939 for col in record.columns.keys():
940 if col.startswith('PTCAUX_'):
941 parts = col.split('PTCAUX_')
942 if isinstance(record[col][0], np.bytes_):
943 # Convert to a unicode string because astropy fits doesn't
944 # round-trip properly
945 inDict['auxValues'][parts[1]] = record[col].astype(np.str_)
946 else:
947 inDict['auxValues'][parts[1]] = record[col]
948
949 return cls().fromDict(inDict)
950
951 def toTable(self):
952 """Construct a list of tables containing the information in this
953 calibration.
954
955 The list of tables should create an identical calibration
956 after being passed to this class's fromTable method.
957
958 Returns
959 -------
960 tableList : `list` [`astropy.table.Table`]
961 List of tables containing the linearity calibration
962 information.
963 """
964 tableList = []
965 self.updateMetadata()
966
967 badAmps = np.array(self.badAmps) if len(self.badAmps) else np.array([], dtype="U3")
968
969 catalogList = []
970 for ampName in self.ampNames:
971 ampDict = {
972 'AMPLIFIER_NAME': ampName,
973 'PTC_FIT_TYPE': self.ptcFitType,
974 'COV_MATRIX_SIDE': self.covMatrixSide,
975 'COV_MATRIX_SIDE_FULL_COV_FIT': self.covMatrixSideFullCovFit,
976 'INPUT_EXP_ID_PAIRS': self.inputExpIdPairs[ampName],
977 'INPUT_EXP_PAIR_MJD_START': self.inputExpPairMjdStartList[ampName],
978 'EXP_ID_MASK': self.expIdMask[ampName],
979 'RAW_EXP_TIMES': self.rawExpTimes[ampName],
980 'RAW_MEANS': self.rawMeans[ampName],
981 'RAW_VARS': self.rawVars[ampName],
982 'RAW_DELTAS': self.rawDeltas[ampName],
983 'ROW_MEAN_VARIANCE': self.rowMeanVariance[ampName],
984 'GAIN': self.gain[ampName],
985 'GAIN_ERR': self.gainErr[ampName],
986 'GAIN_UNADJUSTED': self.gainUnadjusted[ampName],
987 'GAIN_LIST': self.gainList[ampName],
988 'OVERSCAN_MEDIAN_LIST': self.overscanMedianLevelList[ampName],
989 'OVERSCAN_MEDIAN': self.overscanMedian[ampName],
990 'OVERSCAN_MEDIAN_SIGMA': self.overscanMedianSigma[ampName],
991 'NOISE_LIST': self.noiseList[ampName],
992 'NOISE': self.noise[ampName],
993 'NOISE_ERR': self.noiseErr[ampName],
994 'HIST_VARS': self.histVars[ampName],
995 'HIST_CHI2_DOFS': self.histChi2Dofs[ampName],
996 'KS_PVALUES': self.kspValues[ampName],
997 'PTC_FIT_PARS': np.array(self.ptcFitPars[ampName]),
998 'PTC_FIT_PARS_ERROR': np.array(self.ptcFitParsError[ampName]),
999 'PTC_FIT_CHI_SQ': self.ptcFitChiSq[ampName],
1000 'PTC_TURNOFF': self.ptcTurnoff[ampName],
1001 'PTC_TURNOFF_SAMPLING_ERROR': self.ptcTurnoffSamplingError[ampName],
1002 'A_MATRIX': self.aMatrix[ampName].ravel(),
1003 'B_MATRIX': self.bMatrix[ampName].ravel(),
1004 'NOISE_MATRIX': self.noiseMatrix[ampName].ravel(),
1005 'BAD_AMPS': badAmps,
1006 'PHOTO_CHARGE': self.photoCharges[ampName],
1007 'PHOTO_CHARGE_DELTAS': self.photoChargeDeltas[ampName],
1008 'AMP_OFFSETS': self.ampOffsets[ampName],
1009 'NPIXEL_COVARIANCES': self.nPixelCovariances[ampName],
1010 'COVARIANCES': self.covariances[ampName].ravel(),
1011 'COVARIANCES_MODEL': self.covariancesModel[ampName].ravel(),
1012 'COVARIANCES_SQRT_WEIGHTS': self.covariancesSqrtWeights[ampName].ravel(),
1013 'FINAL_VARS': self.finalVars[ampName],
1014 'FINAL_MODEL_VARS': self.finalModelVars[ampName],
1015 'FINAL_MEANS': self.finalMeans[ampName],
1016 }
1017
1018 if self.auxValues:
1019 for key, value in self.auxValues.items():
1020 ampDict[f"PTCAUX_{key}"] = value
1021
1022 catalogList.append(ampDict)
1023
1024 catalog = Table(catalogList)
1025
1026 inMeta = self.getMetadata().toDict()
1027 outMeta = {k: v for k, v in inMeta.items() if v is not None}
1028 outMeta.update({k: "" for k, v in inMeta.items() if v is None})
1029 catalog.meta = outMeta
1030 tableList.append(catalog)
1031
1032 return tableList
1033
1034 def fromDetector(self, detector):
1035 """Read metadata parameters from a detector.
1036
1037 Parameters
1038 ----------
1039 detector : `lsst.afw.cameraGeom.detector`
1040 Input detector with parameters to use.
1041
1042 Returns
1043 -------
1044 calib : `lsst.ip.isr.PhotonTransferCurveDataset`
1045 The calibration constructed from the detector.
1046 """
1047
1048 pass
1049
1050 def appendPartialPtc(self, partialPtc):
1051 """Append a partial PTC dataset to this dataset.
1052
1053 Parameters
1054 ----------
1055 partialPtc : `lsst.ip.isr.PhotonTransferCurveDataset`
1056 Partial PTC to append. Should only have one element.
1057 """
1058 if self.ampNames != partialPtc.ampNames:
1059 raise ValueError("partialPtc has mis-matched amps.")
1060 if len(partialPtc.rawMeans[self.ampNames[0]]) != 1 or partialPtc.ptcFitType != "PARTIAL":
1061 raise ValueError("partialPtc does not appear to be the correct format.")
1062
1063 # Record the initial length of the PTC, for checking auxValues.
1064 initialLength = len(self.expIdMask[self.ampNames[0]])
1065
1066 for key, value in partialPtc.auxValues.items():
1067 if key in self.auxValues:
1068 self.auxValues[key] = np.append(self.auxValues[key], value)
1069 elif initialLength == 0:
1070 # This is the first partial, so we can set the dict key.
1071 self.auxValues[key] = value
1072 else:
1073 raise ValueError(f"partialPtc has mismatched auxValue key {key}.")
1074
1075 for ampName in self.ampNames:
1076 if initialLength == 0:
1077 # This is the first partial, so we can set the dict key.
1078 self.nPixelCovariances[ampName] = partialPtc.nPixelCovariances[ampName]
1079 elif partialPtc.nPixelCovariances[ampName] != self.nPixelCovariances[ampName]:
1080 raise ValueError(f"partialPtc has mismatched nPixelCovariances for amp {ampName}.")
1081
1082 for ampName in self.ampNames:
1083 # The partial dataset consists of lists of values for each
1084 # quantity. In the case of the input exposure pairs and the
1085 # input exposure MJDs, this is a list of tuples. In all cases
1086 # we only want the first (and only) element of the list.
1087 self.inputExpIdPairs[ampName].append(partialPtc.inputExpIdPairs[ampName][0])
1088 self.inputExpPairMjdStartList[ampName] = np.append(
1089 self.inputExpPairMjdStartList[ampName],
1090 partialPtc.inputExpPairMjdStartList[ampName][0],
1091 )
1092 self.expIdMask[ampName] = np.append(self.expIdMask[ampName],
1093 partialPtc.expIdMask[ampName][0])
1094 self.rawExpTimes[ampName] = np.append(self.rawExpTimes[ampName],
1095 partialPtc.rawExpTimes[ampName][0])
1096 self.rawMeans[ampName] = np.append(self.rawMeans[ampName],
1097 partialPtc.rawMeans[ampName][0])
1098 self.rawVars[ampName] = np.append(self.rawVars[ampName],
1099 partialPtc.rawVars[ampName][0])
1100 self.rawDeltas[ampName] = np.append(self.rawDeltas[ampName],
1101 partialPtc.rawDeltas[ampName][0])
1102 self.rowMeanVariance[ampName] = np.append(self.rowMeanVariance[ampName],
1103 partialPtc.rowMeanVariance[ampName][0])
1104 self.photoCharges[ampName] = np.append(self.photoCharges[ampName],
1105 partialPtc.photoCharges[ampName][0])
1106 self.photoChargeDeltas[ampName] = np.append(self.photoChargeDeltas[ampName],
1107 partialPtc.photoChargeDeltas[ampName][0])
1108 self.ampOffsets[ampName] = np.append(self.ampOffsets[ampName],
1109 partialPtc.ampOffsets[ampName][0])
1110 self.histVars[ampName] = np.append(self.histVars[ampName],
1111 partialPtc.histVars[ampName][0])
1112 self.histChi2Dofs[ampName] = np.append(self.histChi2Dofs[ampName],
1113 partialPtc.histChi2Dofs[ampName][0])
1114 self.kspValues[ampName] = np.append(self.kspValues[ampName],
1115 partialPtc.kspValues[ampName][0])
1116 self.gainList[ampName] = np.append(self.gainList[ampName],
1117 partialPtc.gain[ampName])
1118 self.overscanMedianLevelList[ampName] = np.append(
1119 self.overscanMedianLevelList[ampName],
1120 partialPtc.overscanMedianLevelList[ampName][0],
1121 )
1122 self.noiseList[ampName] = np.append(self.noiseList[ampName],
1123 partialPtc.noise[ampName])
1124 self.finalVars[ampName] = np.append(self.finalVars[ampName],
1125 partialPtc.finalVars[ampName][0])
1126 self.finalModelVars[ampName] = np.append(self.finalModelVars[ampName],
1127 partialPtc.finalModelVars[ampName][0])
1128 self.finalMeans[ampName] = np.append(self.finalMeans[ampName],
1129 partialPtc.finalMeans[ampName][0])
1130 self.covariances[ampName] = np.append(
1131 self.covariances[ampName].ravel(),
1132 partialPtc.covariances[ampName].ravel()
1133 ).reshape(
1134 (
1135 len(self.rawExpTimes[ampName]),
1136 self.covMatrixSide,
1137 self.covMatrixSide,
1138 )
1139 )
1140 self.covariancesSqrtWeights[ampName] = np.append(
1141 self.covariancesSqrtWeights[ampName].ravel(),
1142 partialPtc.covariancesSqrtWeights[ampName].ravel()
1143 ).reshape(
1144 (
1145 len(self.rawExpTimes[ampName]),
1146 self.covMatrixSide,
1147 self.covMatrixSide,
1148 )
1149 )
1150 self.covariancesModel[ampName] = np.append(
1151 self.covariancesModel[ampName].ravel(),
1152 partialPtc.covariancesModel[ampName].ravel()
1153 ).reshape(
1154 (
1155 len(self.rawExpTimes[ampName]),
1156 self.covMatrixSide,
1157 self.covMatrixSide,
1158 )
1159 )
1160
1161 def sort(self, sortIndex):
1162 """Sort the components of the PTC by a given sort index.
1163
1164 The PTC is sorted in-place.
1165
1166 Parameters
1167 ----------
1168 sortIndex : `list` or `np.ndarray`
1169 The sorting index, which must be the same length as
1170 the number of elements of the PTC.
1171 """
1172 index = np.atleast_1d(sortIndex)
1173
1174 # First confirm everything matches.
1175 for ampName in self.ampNames:
1176 if len(index) != len(self.rawExpTimes[ampName]):
1177 raise ValueError(
1178 f"Length of sortIndex ({len(index)}) does not match number of PTC "
1179 f"elements ({len(self.rawExpTimes[ampName])})",
1180 )
1181
1182 # Note that gain, gainUnadjusted, gainErr, noise, noiseErr,
1183 # ptcTurnoff, ptcTurnoffSamplingError, and the full covariance fit
1184 # parameters are global and not sorted by input pair.
1185
1186 for ampName in self.ampNames:
1187 self.inputExpIdPairs[ampName] = np.array(
1188 self.inputExpIdPairs[ampName]
1189 )[index].tolist()
1190 self.inputExpPairMjdStartList[ampName] = self.inputExpPairMjdStartList[ampName][index]
1191
1192 self.expIdMask[ampName] = self.expIdMask[ampName][index]
1193 self.rawExpTimes[ampName] = self.rawExpTimes[ampName][index]
1194 self.rawMeans[ampName] = self.rawMeans[ampName][index]
1195 self.rawVars[ampName] = self.rawVars[ampName][index]
1196 self.rawDeltas[ampName] = self.rawDeltas[ampName][index]
1197 self.rowMeanVariance[ampName] = self.rowMeanVariance[ampName][index]
1198 self.photoCharges[ampName] = self.photoCharges[ampName][index]
1199 self.photoChargeDeltas[ampName] = self.photoChargeDeltas[ampName][index]
1200 self.ampOffsets[ampName] = self.ampOffsets[ampName][index]
1201
1202 self.gainList[ampName] = self.gainList[ampName][index]
1203 self.noiseList[ampName] = self.noiseList[ampName][index]
1204
1205 self.overscanMedianLevelList[ampName] = self.overscanMedianLevelList[ampName][index]
1206
1207 self.histVars[ampName] = self.histVars[ampName][index]
1208 self.histChi2Dofs[ampName] = self.histChi2Dofs[ampName][index]
1209 self.kspValues[ampName] = self.kspValues[ampName][index]
1210
1211 self.covariances[ampName] = self.covariances[ampName][index]
1212 self.covariancesSqrtWeights[ampName] = self.covariancesSqrtWeights[ampName][index]
1213 self.covariancesModel[ampName] = self.covariancesModel[ampName][index]
1214
1215 self.finalVars[ampName] = self.finalVars[ampName][index]
1216 self.finalModelVars[ampName] = self.finalModelVars[ampName][index]
1217 self.finalMeans[ampName] = self.finalMeans[ampName][index]
1218
1219 # Sort the auxiliary values which are not stored per-amp.
1220 for key, value in self.auxValues.items():
1221 self.auxValues[key] = value[index]
1222
1223 def getExpIdsUsed(self, ampName):
1224 """Get the exposures used, i.e. not discarded, for a given amp.
1225 If no mask has been created yet, all exposures are returned.
1226
1227 Parameters
1228 ----------
1229 ampName : `str`
1230
1231 Returns
1232 -------
1233 expIdsUsed : `list` [`tuple`]
1234 List of pairs of exposure ids used in PTC.
1235 """
1236 if len(self.expIdMask[ampName]) == 0:
1237 return self.inputExpIdPairs[ampName]
1238
1239 # if the mask exists it had better be the same length as the expIdPairs
1240 assert len(self.expIdMask[ampName]) == len(self.inputExpIdPairs[ampName])
1241
1242 pairs = self.inputExpIdPairs[ampName]
1243 mask = self.expIdMask[ampName]
1244 # cast to bool required because numpy
1245 try:
1246 expIdsUsed = [(exp1, exp2) for ((exp1, exp2), m) in zip(pairs, mask) if m]
1247 except ValueError:
1248 self.log.warning("The PTC file was written incorrectly; you should rerun the "
1249 "PTC solve task if possible.")
1250 expIdsUsed = []
1251 for pairList, m in zip(pairs, mask):
1252 if m:
1253 expIdsUsed.append(pairList[0])
1254
1255 return expIdsUsed
1256
1257 def getGoodAmps(self):
1258 """Get the good amps from this PTC."""
1259 return [amp for amp in self.ampNames if amp not in self.badAmps]
1260
1261 def getGoodPoints(self, ampName):
1262 """Get the good points used for a given amp in the PTC.
1263
1264 Parameters
1265 ----------
1266 ampName : `str`
1267 Amplifier's name.
1268
1269 Returns
1270 -------
1271 goodPoints : `np.ndarray`
1272 Boolean array of good points used in PTC.
1273 """
1274 return self.expIdMask[ampName]
1275
1276 def validateGainNoiseTurnoffValues(self, ampName, doWarn=False):
1277 """Ensure the gain, read noise, and PTC turnoff have
1278 sensible values.
1279
1280 Parameters
1281 ----------
1282 ampName : `str`
1283 Amplifier's name.
1284 """
1285
1286 gain = self.gain[ampName]
1287 noise = self.noise[ampName]
1288 ptcTurnoff = self.ptcTurnoff[ampName]
1289
1290 # Check if gain is not positive or is np.nan
1291 if not (isinstance(gain, (int, float)) and gain > 0) or math.isnan(gain):
1292 if doWarn:
1293 self.log.warning(f"Invalid gain value {gain}"
1294 " Setting to default: Gain=1")
1295 gain = 1
1296
1297 # Check if noise is not positive or is np.nan
1298 if not (isinstance(noise, (int, float)) and noise > 0) or math.isnan(noise):
1299 if doWarn:
1300 self.log.warning(f"Invalid noise value: {noise}"
1301 " Setting to default: Noise=1")
1302 noise = 1
1303
1304 # Check if ptcTurnoff is not positive or is np.nan
1305 if not (isinstance(ptcTurnoff, (int, float)) and ptcTurnoff > 0) or math.isnan(ptcTurnoff):
1306 if doWarn:
1307 self.log.warning(f"Invalid PTC turnoff value: {ptcTurnoff}"
1308 " Setting to default: PTC Turnoff=2e19")
1309 ptcTurnoff = 2e19
1310
1311 self.gain[ampName] = gain
1312 self.noise[ampName] = noise
1313 self.ptcTurnoff[ampName] = ptcTurnoff
1314
1315 def evalPtcModel(self, mu):
1316 """Computes the covariance model at specific signal levels.
1317
1318 Parameters
1319 ----------
1320 mu : `numpy.array`, (N,)
1321 List of mean signals in ADU.
1322
1323 Raises
1324 ------
1325 RuntimeError
1326 Raised if ptcFitType is invalid.
1327
1328 Returns
1329 -------
1330 covModel : `numpy.array`, (N, M, M)
1331 Covariances model at mu (in ADU^2).
1332
1333 Notes
1334 -----
1335 Computes the covModel for all mu, and it returns
1336 cov[N, M, M], where the variance model is cov[:,0,0].
1337 Both mu and cov are in ADUs and ADUs squared. This
1338 routine evaulates the n-degree polynomial model (defined
1339 by polynomialFitDegree) if self.ptcFitType == POLYNOMIAL,
1340 the approximation in Eq. 16 of Astier+19 (1905.08677)
1341 if self.ptcFitType == EXPAPPROXIMATION, and Eq. 20 of
1342 Astier+19 if self.ptcFitType == FULLCOVARIANCE.
1343
1344 The POLYNOMIAL model and the EXPAPPROXIMATION model
1345 (Eq. 16 of Astier+19) are only approximations for the
1346 variance (cov[0,0]), so the function returns covModel
1347 of shape (N,), representing an array of [C_{00}]
1348 if self.ptcFitType == EXPAPPROXIMATION or
1349 self.ptcFitType == POLYNOMAIL.
1350 """
1351
1352 ampNames = self.ampNames
1353 covModel = {ampName: np.array([]) for ampName in ampNames}
1354
1355 if self.ptcFitType == "POLYNOMIAL":
1356 pars = self.ptcFitPars
1357
1358 for ampName in ampNames:
1359 c00 = poly.polyval(mu, [*pars[ampName]])
1360 covModel[ampName] = c00
1361
1362 elif self.ptcFitType == "EXPAPPROXIMATION":
1363 pars = self.ptcFitPars
1364
1365 for ampName in ampNames:
1366 a00, gain, noise = pars[ampName]
1367 f1 = 0.5/(a00*gain*gain)*(np.exp(2*a00*mu*gain)-1)
1368 f2 = noise/(gain*gain)
1369 c00 = f1 + f2
1370 covModel[ampName] = c00
1371
1372 elif self.ptcFitType in ["FULLCOVARIANCE", "FULLCOVARIANCE_NO_B"]:
1373 for ampName in ampNames:
1374 noiseMatrix = self.noiseMatrix[ampName]
1375 gain = self.gain[ampName]
1376 aMatrix = self.aMatrix[ampName]
1377 bMatrix = self.bMatrix[ampName]
1378 cMatrix = aMatrix*bMatrix
1379
1380 matrixSideFit = self.covMatrixSideFullCovFit
1381 sa = (matrixSideFit, matrixSideFit)
1382
1383 # pad a with zeros and symmetrize
1384 aEnlarged = np.zeros((int(sa[0]*1.5)+1, int(sa[1]*1.5)+1))
1385 aEnlarged[0:sa[0], 0:sa[1]] = aMatrix
1386 aSym = symmetrize(aEnlarged)
1387
1388 # pad c with zeros and symmetrize
1389 cEnlarged = np.zeros((int(sa[0]*1.5)+1, int(sa[1]*1.5)+1))
1390 cEnlarged[0:sa[0], 0:sa[1]] = cMatrix
1391
1392 cSym = symmetrize(cEnlarged)
1393 a2 = fftconvolve(aSym, aSym, mode='same')
1394 a3 = fftconvolve(a2, aSym, mode='same')
1395 ac = fftconvolve(aSym, cSym, mode='same')
1396 (xc, yc) = np.unravel_index(np.abs(aSym).argmax(), a2.shape)
1397
1398 a1 = aMatrix[np.newaxis, :, :]
1399 a2 = a2[np.newaxis, xc:xc + matrixSideFit, yc:yc + matrixSideFit]
1400 a3 = a3[np.newaxis, xc:xc + matrixSideFit, yc:yc + matrixSideFit]
1401 ac = ac[np.newaxis, xc:xc + matrixSideFit, yc:yc + matrixSideFit]
1402 c1 = cMatrix[np.newaxis, ::]
1403
1404 # assumes that mu is 1d
1405 bigMu = mu[:, np.newaxis, np.newaxis]*gain
1406 # c(=a*b in Astier+19) also has a contribution to the last
1407 # term, that is absent for now.
1408
1409 covModel[ampName] = (bigMu/(gain*gain)*(a1*bigMu+2./3.*(bigMu*bigMu)*(a2 + c1)
1410 + (1./3.*a3 + 5./6.*ac)*(bigMu*bigMu*bigMu))
1411 + noiseMatrix[np.newaxis, :, :]/gain**2)
1412
1413 # add the Poisson term, and the read out noise (variance)
1414 covModel[ampName][:, 0, 0] += mu/gain
1415 else:
1416 raise RuntimeError("Cannot compute PTC model for "
1417 "ptcFitType %s." % self.ptcFitType)
1418
1419 return covModel
1420
1422 """Ensure covMatrixSideFullCovFit <= covMatrixSide."""
1424 self.log.warning("covMatrixSideFullCovFit > covMatrixSide "
1425 f"({self.covMatrixSideFullCovFit} > {self.covMatrixSide})."
1426 "Setting the former to the latter.")
updateMetadata(self, camera=None, detector=None, filterName=None, setCalibId=False, setCalibInfo=False, setDate=False, **kwargs)
Definition calibType.py:210
setAmpValuesPartialDataset(self, ampName, inputExpIdPair=(-1, -1), inputExpPairMjdStart=np.nan, rawExpTime=np.nan, rawMean=np.nan, rawVar=np.nan, rawDelta=np.nan, rowMeanVariance=np.nan, photoCharge=np.nan, photoChargeDelta=np.nan, ampOffset=np.nan, expIdMask=False, nPixelCovariance=-1, covariance=None, covSqrtWeights=None, gain=np.nan, noise=np.nan, overscanMedianLevel=np.nan, histVar=np.nan, histChi2Dof=np.nan, kspValue=0.0)
validateGainNoiseTurnoffValues(self, ampName, doWarn=False)
__init__(self, ampNames=[], ptcFitType=None, covMatrixSide=1, covMatrixSideFullCovFit=None, **kwargs)