331 inputExpIdPair=(-1, -1),
335 rowMeanVariance=np.nan,
348 Set the amp values for a partial PTC Dataset (from cpExtractPtcTask).
353 Name of the amp to set the values.
354 inputExpIdPair : `tuple` [`int`]
355 Exposure IDs of input pair.
356 rawExpTime : `float`, optional
357 Exposure time for this exposure pair.
358 rawMean : `float`, optional
359 Average of the means of the exposures in this pair.
360 rawVar : `float`, optional
361 Variance of the difference of the exposures in this pair.
362 rowMeanVariance : `float`, optional
363 Variance of the means of the rows in the difference image
364 of the exposures in this pair.
365 photoCharge : `float`, optional
366 Integrated photocharge for flat pair for linearity calibration.
367 ampOffset : `float`, optional
368 Amp offset for this amplifier.
369 expIdMask : `bool`, optional
370 Flag setting if this exposure pair should be used (True)
372 covariance : `np.ndarray` or None, optional
373 Measured covariance for this exposure pair.
374 covSqrtWeights : `np.ndarray` or None, optional
375 Measured sqrt of covariance weights in this exposure pair.
376 gain : `float`, optional
377 Estimated gain for this exposure pair.
378 noise : `float`, optional
379 Estimated read noise for this exposure pair.
380 histVar : `float`, optional
381 Variance estimated from fitting a histogram with a Gaussian model.
382 histChi2Dof : `float`, optional
383 Chi-squared per degree of freedom from Gaussian histogram fit.
384 kspValue : `float`, optional
385 KS test p-value from the Gaussian histogram fit.
390 if covariance
is None:
391 covariance = nanMatrix
392 if covSqrtWeights
is None:
393 covSqrtWeights = nanMatrix
397 self.
rawMeans[ampName] = np.array([rawMean])
398 self.
rawVars[ampName] = np.array([rawVar])
401 self.
ampOffsets[ampName] = np.array([ampOffset])
402 self.
expIdMask[ampName] = np.array([expIdMask])
405 self.
gain[ampName] = gain
407 self.
gainList[ampName] = np.array([gain])
408 self.
noise[ampName] = noise
409 self.
noiseList[ampName] = np.array([noise])
410 self.
histVars[ampName] = np.array([histVar])
412 self.
kspValues[ampName] = np.array([kspValue])
417 self.
aMatrix[ampName] = nanMatrixFit
418 self.
bMatrix[ampName] = nanMatrixFit
424 self.
finalVars[ampName] = np.array([np.nan])
462 """Construct a calibration from a dictionary of properties.
463 Must be implemented by the specific calibration subclasses.
468 Dictionary of properties.
472 calib : `lsst.ip.isr.PhotonTransferCurveDataset`
473 Constructed calibration.
478 Raised if the supplied dictionary is for a different
482 if calib._OBSTYPE != dictionary[
'metadata'][
'OBSTYPE']:
483 raise RuntimeError(f
"Incorrect Photon Transfer Curve dataset supplied. "
484 f
"Expected {calib._OBSTYPE}, found {dictionary['metadata']['OBSTYPE']}")
485 calib.setMetadata(dictionary[
'metadata'])
486 calib.ptcFitType = dictionary[
'ptcFitType']
487 calib.covMatrixSide = dictionary[
'covMatrixSide']
488 calib.covMatrixSideFullCovFit = dictionary[
'covMatrixSideFullCovFit']
489 calib.badAmps = np.array(dictionary[
'badAmps'],
'str').tolist()
493 covMatrixSide = calib.covMatrixSide
494 covMatrixSideFullCovFit = calib.covMatrixSideFullCovFit
496 covDimensionsProduct = len(np.array(list(dictionary[
'covariances'].values())[0]).ravel())
497 nSignalPoints = int(covDimensionsProduct/(covMatrixSide*covMatrixSide))
499 for ampName
in dictionary[
'ampNames']:
500 calib.ampNames.append(ampName)
501 calib.inputExpIdPairs[ampName] = dictionary[
'inputExpIdPairs'][ampName]
502 calib.expIdMask[ampName] = np.array(dictionary[
'expIdMask'][ampName])
503 calib.rawExpTimes[ampName] = np.array(dictionary[
'rawExpTimes'][ampName], dtype=np.float64)
504 calib.rawMeans[ampName] = np.array(dictionary[
'rawMeans'][ampName], dtype=np.float64)
505 calib.rawVars[ampName] = np.array(dictionary[
'rawVars'][ampName], dtype=np.float64)
506 calib.rowMeanVariance[ampName] = np.array(dictionary[
'rowMeanVariance'][ampName],
508 calib.gain[ampName] = float(dictionary[
'gain'][ampName])
509 calib.gainErr[ampName] = float(dictionary[
'gainErr'][ampName])
510 calib.gainUnadjusted[ampName] = float(dictionary[
'gainUnadjusted'][ampName])
511 calib.gainList[ampName] = np.array(dictionary[
'gainList'][ampName], dtype=np.float64)
512 calib.noiseList[ampName] = np.array(dictionary[
'noiseList'][ampName], dtype=np.float64)
513 calib.noise[ampName] = float(dictionary[
'noise'][ampName])
514 calib.noiseErr[ampName] = float(dictionary[
'noiseErr'][ampName])
515 calib.histVars[ampName] = np.array(dictionary[
'histVars'][ampName], dtype=np.float64)
516 calib.histChi2Dofs[ampName] = np.array(dictionary[
'histChi2Dofs'][ampName], dtype=np.float64)
517 calib.kspValues[ampName] = np.array(dictionary[
'kspValues'][ampName], dtype=np.float64)
518 calib.ptcFitPars[ampName] = np.array(dictionary[
'ptcFitPars'][ampName], dtype=np.float64)
519 calib.ptcFitParsError[ampName] = np.array(dictionary[
'ptcFitParsError'][ampName],
521 calib.ptcFitChiSq[ampName] = float(dictionary[
'ptcFitChiSq'][ampName])
522 calib.ptcTurnoff[ampName] = float(dictionary[
'ptcTurnoff'][ampName])
523 calib.ptcTurnoffSamplingError[ampName] = float(dictionary[
'ptcTurnoffSamplingError'][ampName])
524 if nSignalPoints > 0:
526 calib.covariances[ampName] = np.array(dictionary[
'covariances'][ampName],
527 dtype=np.float64).reshape(
528 (nSignalPoints, covMatrixSide, covMatrixSide))
529 calib.covariancesModel[ampName] = np.array(
530 dictionary[
'covariancesModel'][ampName],
531 dtype=np.float64).reshape(
532 (nSignalPoints, covMatrixSideFullCovFit, covMatrixSideFullCovFit))
533 calib.covariancesSqrtWeights[ampName] = np.array(
534 dictionary[
'covariancesSqrtWeights'][ampName],
535 dtype=np.float64).reshape(
536 (nSignalPoints, covMatrixSide, covMatrixSide))
537 calib.aMatrix[ampName] = np.array(dictionary[
'aMatrix'][ampName],
538 dtype=np.float64).reshape(
539 (covMatrixSideFullCovFit, covMatrixSideFullCovFit))
540 calib.bMatrix[ampName] = np.array(dictionary[
'bMatrix'][ampName],
541 dtype=np.float64).reshape(
542 (covMatrixSideFullCovFit, covMatrixSideFullCovFit))
543 calib.covariancesModelNoB[ampName] = np.array(
544 dictionary[
'covariancesModelNoB'][ampName], dtype=np.float64).reshape(
545 (nSignalPoints, covMatrixSideFullCovFit, covMatrixSideFullCovFit))
546 calib.aMatrixNoB[ampName] = np.array(
547 dictionary[
'aMatrixNoB'][ampName],
548 dtype=np.float64).reshape((covMatrixSideFullCovFit, covMatrixSideFullCovFit))
549 calib.noiseMatrix[ampName] = np.array(
550 dictionary[
'noiseMatrix'][ampName],
551 dtype=np.float64).reshape((covMatrixSideFullCovFit, covMatrixSideFullCovFit))
552 calib.noiseMatrixNoB[ampName] = np.array(
553 dictionary[
'noiseMatrixNoB'][ampName],
554 dtype=np.float64).reshape((covMatrixSideFullCovFit, covMatrixSideFullCovFit))
557 calib.covariances[ampName] = np.array([], dtype=np.float64)
558 calib.covariancesModel[ampName] = np.array([], dtype=np.float64)
559 calib.covariancesSqrtWeights[ampName] = np.array([], dtype=np.float64)
560 calib.aMatrix[ampName] = np.array([], dtype=np.float64)
561 calib.bMatrix[ampName] = np.array([], dtype=np.float64)
562 calib.covariancesModelNoB[ampName] = np.array([], dtype=np.float64)
563 calib.aMatrixNoB[ampName] = np.array([], dtype=np.float64)
564 calib.noiseMatrix[ampName] = np.array([], dtype=np.float64)
565 calib.noiseMatrixNoB[ampName] = np.array([], dtype=np.float64)
567 calib.finalVars[ampName] = np.array(dictionary[
'finalVars'][ampName], dtype=np.float64)
568 calib.finalModelVars[ampName] = np.array(dictionary[
'finalModelVars'][ampName], dtype=np.float64)
569 calib.finalMeans[ampName] = np.array(dictionary[
'finalMeans'][ampName], dtype=np.float64)
570 calib.photoCharges[ampName] = np.array(dictionary[
'photoCharges'][ampName], dtype=np.float64)
571 calib.ampOffsets[ampName] = np.array(dictionary[
'ampOffsets'][ampName], dtype=np.float64)
573 for key, value
in dictionary[
'auxValues'].
items():
574 if isinstance(value[0], numbers.Integral):
575 calib.auxValues[key] = np.atleast_1d(np.asarray(value).astype(np.int64))
576 elif isinstance(value[0], (str, np.str_, np.string_)):
577 calib.auxValues[key] = np.atleast_1d(np.asarray(value))
579 calib.auxValues[key] = np.atleast_1d(np.array(value, dtype=np.float64))
581 calib.updateMetadata()
585 """Return a dictionary containing the calibration properties.
586 The dictionary should be able to be round-tripped through
592 Dictionary of properties.
598 outDict[
'metadata'] = metadata
600 def _dictOfArraysToDictOfLists(dictOfArrays):
602 for key, value
in dictOfArrays.items():
603 dictOfLists[key] = value.ravel().tolist()
611 outDict[
'badAmps'] = self.
badAmps
613 outDict[
'expIdMask'] = _dictOfArraysToDictOfLists(self.
expIdMask)
614 outDict[
'rawExpTimes'] = _dictOfArraysToDictOfLists(self.
rawExpTimes)
615 outDict[
'rawMeans'] = _dictOfArraysToDictOfLists(self.
rawMeans)
616 outDict[
'rawVars'] = _dictOfArraysToDictOfLists(self.
rawVars)
617 outDict[
'rowMeanVariance'] = _dictOfArraysToDictOfLists(self.
rowMeanVariance)
618 outDict[
'gain'] = self.
gain
619 outDict[
'gainErr'] = self.
gainErr
621 outDict[
'gainList'] = _dictOfArraysToDictOfLists(self.
gainList)
622 outDict[
'noiseList'] = _dictOfArraysToDictOfLists(self.
noiseList)
623 outDict[
'noise'] = self.
noise
628 outDict[
'ptcFitPars'] = _dictOfArraysToDictOfLists(self.
ptcFitPars)
629 outDict[
'ptcFitParsError'] = _dictOfArraysToDictOfLists(self.
ptcFitParsError)
633 outDict[
'covariances'] = _dictOfArraysToDictOfLists(self.
covariances)
634 outDict[
'covariancesModel'] = _dictOfArraysToDictOfLists(self.
covariancesModel)
636 outDict[
'aMatrix'] = _dictOfArraysToDictOfLists(self.
aMatrix)
637 outDict[
'bMatrix'] = _dictOfArraysToDictOfLists(self.
bMatrix)
638 outDict[
'noiseMatrix'] = _dictOfArraysToDictOfLists(self.
noiseMatrix)
640 outDict[
'aMatrixNoB'] = _dictOfArraysToDictOfLists(self.
aMatrixNoB)
641 outDict[
'noiseMatrixNoB'] = _dictOfArraysToDictOfLists(self.
noiseMatrixNoB)
642 outDict[
'finalVars'] = _dictOfArraysToDictOfLists(self.
finalVars)
643 outDict[
'finalModelVars'] = _dictOfArraysToDictOfLists(self.
finalModelVars)
644 outDict[
'finalMeans'] = _dictOfArraysToDictOfLists(self.
finalMeans)
645 outDict[
'photoCharges'] = _dictOfArraysToDictOfLists(self.
photoCharges)
646 outDict[
'ampOffsets'] = _dictOfArraysToDictOfLists(self.
ampOffsets)
647 outDict[
'auxValues'] = _dictOfArraysToDictOfLists(self.
auxValues)
925 """Append a partial PTC dataset to this dataset.
929 partialPtc : `lsst.ip.isr.PhotonTransferCurveDataset`
930 Partial PTC to append. Should only have one element.
932 if self.ampNames != partialPtc.ampNames:
933 raise ValueError(
"partialPtc has mis-matched amps.")
934 if len(partialPtc.rawMeans[self.ampNames[0]]) != 1
or partialPtc.ptcFitType !=
"PARTIAL":
935 raise ValueError(
"partialPtc does not appear to be the correct format.")
938 initialLength = len(self.expIdMask[self.ampNames[0]])
940 for key, value
in partialPtc.auxValues.items():
941 if key
in self.auxValues:
942 self.auxValues[key] = np.append(self.auxValues[key], value)
943 elif initialLength == 0:
945 self.auxValues[key] = value
947 raise ValueError(f
"partialPtc has mismatched auxValue key {key}.")
949 for ampName
in self.ampNames:
954 self.inputExpIdPairs[ampName].append(partialPtc.inputExpIdPairs[ampName][0])
955 self.expIdMask[ampName] = np.append(self.expIdMask[ampName],
956 partialPtc.expIdMask[ampName][0])
957 self.rawExpTimes[ampName] = np.append(self.rawExpTimes[ampName],
958 partialPtc.rawExpTimes[ampName][0])
959 self.rawMeans[ampName] = np.append(self.rawMeans[ampName],
960 partialPtc.rawMeans[ampName][0])
961 self.rawVars[ampName] = np.append(self.rawVars[ampName],
962 partialPtc.rawVars[ampName][0])
963 self.rowMeanVariance[ampName] = np.append(self.rowMeanVariance[ampName],
964 partialPtc.rowMeanVariance[ampName][0])
965 self.photoCharges[ampName] = np.append(self.photoCharges[ampName],
966 partialPtc.photoCharges[ampName][0])
967 self.ampOffsets[ampName] = np.append(self.ampOffsets[ampName],
968 partialPtc.ampOffsets[ampName][0])
969 self.histVars[ampName] = np.append(self.histVars[ampName],
970 partialPtc.histVars[ampName][0])
971 self.histChi2Dofs[ampName] = np.append(self.histChi2Dofs[ampName],
972 partialPtc.histChi2Dofs[ampName][0])
973 self.kspValues[ampName] = np.append(self.kspValues[ampName],
974 partialPtc.kspValues[ampName][0])
975 self.gainList[ampName] = np.append(self.gainList[ampName],
976 partialPtc.gain[ampName])
977 self.noiseList[ampName] = np.append(self.noiseList[ampName],
978 partialPtc.noise[ampName])
979 self.finalVars[ampName] = np.append(self.finalVars[ampName],
980 partialPtc.finalVars[ampName][0])
981 self.finalModelVars[ampName] = np.append(self.finalModelVars[ampName],
982 partialPtc.finalModelVars[ampName][0])
983 self.finalMeans[ampName] = np.append(self.finalMeans[ampName],
984 partialPtc.finalMeans[ampName][0])
986 self.covariances[ampName] = np.append(
987 self.covariances[ampName].ravel(),
988 partialPtc.covariances[ampName].ravel()
991 len(self.rawExpTimes[ampName]),
996 self.covariancesSqrtWeights[ampName] = np.append(
997 self.covariancesSqrtWeights[ampName].ravel(),
998 partialPtc.covariancesSqrtWeights[ampName].ravel()
1001 len(self.rawExpTimes[ampName]),
1006 self.covariancesModel[ampName] = np.append(
1007 self.covariancesModel[ampName].ravel(),
1008 partialPtc.covariancesModel[ampName].ravel()
1011 len(self.rawExpTimes[ampName]),
1016 self.covariancesModelNoB[ampName] = np.append(
1017 self.covariancesModelNoB[ampName].ravel(),
1018 partialPtc.covariancesModelNoB[ampName].ravel()
1021 len(self.rawExpTimes[ampName]),
1178 """Computes the covariance model at specific signal levels.
1182 mu : `numpy.array`, (N,)
1183 List of mean signals in ADU.
1184 setBtoZero: `bool`, optional
1185 Set "b" parameter in full model (see Astier+19) to zero.
1186 This parameter is ignored if ptcFitType != FULLCOVARIANCE.
1191 Raised if ptcFitType is invalid.
1195 covModel : `numpy.array`, (N, M, M)
1196 Covariances model at mu (in ADU^2).
1200 Computes the covModel for all mu, and it returns
1201 cov[N, M, M], where the variance model is cov[:,0,0].
1202 Both mu and cov are in ADUs and ADUs squared. This
1203 routine evaulates the n-degree polynomial model (defined
1204 by polynomialFitDegree) if self.ptcFitType == POLYNOMIAL,
1205 the approximation in Eq. 16 of Astier+19 (1905.08677)
1206 if self.ptcFitType == EXPAPPROXIMATION, and Eq. 20 of
1207 Astier+19 if self.ptcFitType == FULLCOVARIANCE.
1209 The POLYNOMIAL model and the EXPAPPROXIMATION model
1210 (Eq. 16 of Astier+19) are only approximations for the
1211 variance (cov[0,0]), so the function returns covModel
1212 of shape (N,), representing an array of [C_{00}]
1213 if self.ptcFitType == EXPAPPROXIMATION or
1214 self.ptcFitType == POLYNOMAIL.
1216 Note that the PhotoTransferCurveDataset does not store
1217 the gain fit parameter for FULLCOVARIANCE with b=0, so
1218 we can't recompute the covariance model with
1219 setBtoZero=True exactly.
1223 covModel = {ampName: np.array([])
for ampName
in ampNames}
1228 for ampName
in ampNames:
1229 c00 = poly.polyval(mu, [*pars[ampName]])
1230 covModel[ampName] = c00
1235 for ampName
in ampNames:
1236 a00, gain, noise = pars[ampName]
1237 f1 = 0.5/(a00*gain*gain)*(np.exp(2*a00*mu*gain)-1)
1238 f2 = noise/(gain*gain)
1240 covModel[ampName] = c00
1243 for ampName
in ampNames:
1245 gain = self.
gain[ampName]
1246 aMatrix = self.
aMatrix[ampName]
1247 bMatrix = self.
bMatrix[ampName]
1248 cMatrix = aMatrix*bMatrix
1255 raise NotImplementedError(
"Cannot evaulate the PTC model"
1256 "with b=0 for FULLCOVARIANCE.")
1259 sa = (matrixSideFit, matrixSideFit)
1262 aEnlarged = np.zeros((int(sa[0]*1.5)+1, int(sa[1]*1.5)+1))
1263 aEnlarged[0:sa[0], 0:sa[1]] = aMatrix
1267 cEnlarged = np.zeros((int(sa[0]*1.5)+1, int(sa[1]*1.5)+1))
1268 cEnlarged[0:sa[0], 0:sa[1]] = cMatrix
1271 a2 = fftconvolve(aSym, aSym, mode=
'same')
1272 a3 = fftconvolve(a2, aSym, mode=
'same')
1273 ac = fftconvolve(aSym, cSym, mode=
'same')
1274 (xc, yc) = np.unravel_index(np.abs(aSym).argmax(), a2.shape)
1276 a1 = aMatrix[np.newaxis, :, :]
1277 a2 = a2[np.newaxis, xc:xc + matrixSideFit, yc:yc + matrixSideFit]
1278 a3 = a3[np.newaxis, xc:xc + matrixSideFit, yc:yc + matrixSideFit]
1279 ac = ac[np.newaxis, xc:xc + matrixSideFit, yc:yc + matrixSideFit]
1280 c1 = cMatrix[np.newaxis, ::]
1283 bigMu = mu[:, np.newaxis, np.newaxis]*gain
1287 c1 = np.zeros_like(c1)
1288 ac = np.zeros_like(ac)
1290 covModel[ampName] = (bigMu/(gain*gain)*(a1*bigMu+2./3.*(bigMu*bigMu)*(a2 + c1)
1291 + (1./3.*a3 + 5./6.*ac)*(bigMu*bigMu*bigMu))
1292 + noiseMatrix[np.newaxis, :, :]/gain**2)
1295 covModel[ampName][:, 0, 0] += mu/gain
1297 raise RuntimeError(
"Cannot compute PTC model for "