-
Notifications
You must be signed in to change notification settings - Fork 14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
DM-46509: Add FULLCOVRIANCE_NO_B fit option to PTC solver #354
Open
Alex-Broughton
wants to merge
5
commits into
main
Choose a base branch
from
tickets/DM-46509
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
c0bbe73
Add gainNoB, gainNoBErr, and gainNoBUnadjusted attributes to ptcDataset
Alex-Broughton f726aa5
Evaluate PTC model for FULCOVARIANCE fit if b=0
Alex-Broughton 089773f
Add FULLCOVARIANCENOB to evalPtcModel
Alex-Broughton ca222a7
Record overscan statistics units
Alex-Broughton 5cd6120
Record values of deprecated attributes when reading old PTC versions
Alex-Broughton File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,6 +34,8 @@ | |
|
||
from lsst.ip.isr import IsrCalib | ||
|
||
from deprecated.sphinx import deprecated | ||
|
||
|
||
def symmetrize(inputArray): | ||
""" Copy array over 4 quadrants prior to convolution. | ||
|
@@ -191,15 +193,16 @@ class PhotonTransferCurveDataset(IsrCalib): | |
the model in Eq. 20 of Astier+19 (units: electron^2). | ||
covariancesModelNoB : `dict`, [`str`, `np.ndarray`] | ||
Dictionary keyed by amp names containing covariances model | ||
(with 'b'=0 in Eq. 20 of Astier+19) | ||
per mean flux (units: adu^2). | ||
(with 'b'=0 in Eq. 20 of Astier+19) per mean flux (units: | ||
adu^2). Will be deprecated in v28. | ||
aMatrixNoB : `dict`, [`str`, `np.ndarray`] | ||
Dictionary keyed by amp names containing the "a" parameters from the | ||
model in Eq. 20 of Astier+19 | ||
(and 'b' = 0) (units: 1/electron). | ||
model in Eq. 20 of Astier+19 (and 'b' = 0) (units: 1/electron). | ||
Will be deprecated in v28. | ||
noiseMatrixNoB : `dict`, [`str`, `np.ndarray`] | ||
Dictionary keyed by amp names containing the "noise" parameters from | ||
the model in Eq. 20 of Astier+19, with 'b' = 0 (units: electron^2). | ||
Will be deprecated in v28. | ||
finalVars : `dict`, [`str`, `np.ndarray`] | ||
Dictionary keyed by amp names containing the masked variance of the | ||
difference image of each flat | ||
|
@@ -234,6 +237,8 @@ class PhotonTransferCurveDataset(IsrCalib): | |
Version 1.9 standardizes PTC noise units to electron. | ||
Version 2.0 adds the `ampOffsets`, `gainUnadjusted`, and | ||
`gainList` attributes. | ||
Version 2.1 removes the `covariancesModelNoB`, `aMatrixNoB`, and | ||
`noiseMatrixNoB` attributes. | ||
""" | ||
|
||
_OBSTYPE = 'PTC' | ||
|
@@ -252,7 +257,7 @@ class PhotonTransferCurveDataset(IsrCalib): | |
# * test_ptcDataset() in test_ptcDataset.py | ||
# * test_ptcDatasetSort in test_ptcDataset.py | ||
# * test_ptcDatasetAppend in test_ptcDataset.py | ||
_VERSION = 2.0 | ||
_VERSION = 2.1 | ||
|
||
def __init__(self, ampNames=[], ptcFitType=None, covMatrixSide=1, | ||
covMatrixSideFullCovFit=None, **kwargs): | ||
|
@@ -299,9 +304,9 @@ def __init__(self, ampNames=[], ptcFitType=None, covMatrixSide=1, | |
self.aMatrix = {ampName: np.array([]) for ampName in ampNames} | ||
self.bMatrix = {ampName: np.array([]) for ampName in ampNames} | ||
self.noiseMatrix = {ampName: np.array([]) for ampName in ampNames} | ||
self.covariancesModelNoB = {ampName: np.array([]) for ampName in ampNames} | ||
self.aMatrixNoB = {ampName: np.array([]) for ampName in ampNames} | ||
self.noiseMatrixNoB = {ampName: np.array([]) for ampName in ampNames} | ||
self._covariancesModelNoB = {ampName: np.array([]) for ampName in ampNames} | ||
self._aMatrixNoB = {ampName: np.array([]) for ampName in ampNames} | ||
self._noiseMatrixNoB = {ampName: np.array([]) for ampName in ampNames} | ||
|
||
self.finalVars = {ampName: np.array([]) for ampName in ampNames} | ||
self.finalModelVars = {ampName: np.array([]) for ampName in ampNames} | ||
|
@@ -315,9 +320,8 @@ def __init__(self, ampNames=[], ptcFitType=None, covMatrixSide=1, | |
'rawMeans', 'rawVars', 'rowMeanVariance', 'gain', | ||
'gainErr', 'noise', 'noiseErr', 'noiseList', | ||
'ptcFitPars', 'ptcFitParsError', 'ptcFitChiSq', 'ptcTurnoff', | ||
'aMatrixNoB', 'covariances', 'covariancesModel', | ||
'covariancesSqrtWeights', 'covariancesModelNoB', | ||
'aMatrix', 'bMatrix', 'noiseMatrix', 'noiseMatrixNoB', 'finalVars', | ||
'covariances', 'covariancesModel', 'covariancesSqrtWeights', | ||
'aMatrix', 'bMatrix', 'noiseMatrix', 'finalVars', | ||
'finalModelVars', 'finalMeans', 'photoCharges', 'histVars', | ||
'histChi2Dofs', 'kspValues', 'auxValues', 'ptcTurnoffSamplingError', | ||
'ampOffsets', 'gainUnadjusted', 'gainList']) | ||
|
@@ -413,18 +417,18 @@ def setAmpValuesPartialDataset( | |
|
||
# From FULLCOVARIANCE model | ||
self.covariancesModel[ampName] = np.array([nanMatrixFit]) | ||
self.covariancesModelNoB[ampName] = np.array([nanMatrixFit]) | ||
self.aMatrix[ampName] = nanMatrixFit | ||
self.bMatrix[ampName] = nanMatrixFit | ||
self.aMatrixNoB[ampName] = nanMatrixFit | ||
self.noiseMatrix[ampName] = nanMatrixFit | ||
self.noiseMatrixNoB[ampName] = nanMatrixFit | ||
|
||
# Filler values. | ||
self.finalVars[ampName] = np.array([np.nan]) | ||
self.finalModelVars[ampName] = np.array([np.nan]) | ||
self.finalMeans[ampName] = np.array([np.nan]) | ||
|
||
self._covariancesModelNoB[ampName] = np.array([nanMatrixFit]) | ||
self._aMatrixNoB[ampName] = nanMatrixFit | ||
self._noiseMatrixNoB[ampName] = nanMatrixFit | ||
|
||
def setAuxValuesPartialDataset(self, auxDict): | ||
""" | ||
Set a dictionary of auxiliary values for a partial dataset. | ||
|
@@ -489,6 +493,8 @@ def fromDict(cls, dictionary): | |
calib.badAmps = np.array(dictionary['badAmps'], 'str').tolist() | ||
calib.ampNames = [] | ||
|
||
calibVersion = dictionary['metadata']['PTC_VERSION'] | ||
|
||
# The cov matrices are square | ||
covMatrixSide = calib.covMatrixSide | ||
covMatrixSideFullCovFit = calib.covMatrixSideFullCovFit | ||
|
@@ -540,29 +546,30 @@ def fromDict(cls, dictionary): | |
calib.bMatrix[ampName] = np.array(dictionary['bMatrix'][ampName], | ||
dtype=np.float64).reshape( | ||
(covMatrixSideFullCovFit, covMatrixSideFullCovFit)) | ||
calib.covariancesModelNoB[ampName] = np.array( | ||
dictionary['covariancesModelNoB'][ampName], dtype=np.float64).reshape( | ||
(nSignalPoints, covMatrixSideFullCovFit, covMatrixSideFullCovFit)) | ||
calib.aMatrixNoB[ampName] = np.array( | ||
dictionary['aMatrixNoB'][ampName], | ||
dtype=np.float64).reshape((covMatrixSideFullCovFit, covMatrixSideFullCovFit)) | ||
calib.noiseMatrix[ampName] = np.array( | ||
dictionary['noiseMatrix'][ampName], | ||
dtype=np.float64).reshape((covMatrixSideFullCovFit, covMatrixSideFullCovFit)) | ||
calib.noiseMatrixNoB[ampName] = np.array( | ||
dictionary['noiseMatrixNoB'][ampName], | ||
dtype=np.float64).reshape((covMatrixSideFullCovFit, covMatrixSideFullCovFit)) | ||
|
||
# Deprecated to be removed after v28 | ||
if calibVersion < 2.1: | ||
calib._covariancesModelNoB[ampName] = np.array( | ||
dictionary['covariancesModelNoB'][ampName], dtype=np.float64).reshape( | ||
(nSignalPoints, covMatrixSideFullCovFit, covMatrixSideFullCovFit)) | ||
calib._aMatrixNoB[ampName] = np.array( | ||
dictionary['aMatrixNoB'][ampName], | ||
dtype=np.float64).reshape((covMatrixSideFullCovFit, covMatrixSideFullCovFit)) | ||
calib._noiseMatrixNoB[ampName] = np.array( | ||
dictionary['noiseMatrixNoB'][ampName], | ||
dtype=np.float64).reshape((covMatrixSideFullCovFit, covMatrixSideFullCovFit)) | ||
|
||
else: | ||
# Empty dataset | ||
calib.covariances[ampName] = np.array([], dtype=np.float64) | ||
calib.covariancesModel[ampName] = np.array([], dtype=np.float64) | ||
calib.covariancesSqrtWeights[ampName] = np.array([], dtype=np.float64) | ||
calib.aMatrix[ampName] = np.array([], dtype=np.float64) | ||
calib.bMatrix[ampName] = np.array([], dtype=np.float64) | ||
calib.covariancesModelNoB[ampName] = np.array([], dtype=np.float64) | ||
calib.aMatrixNoB[ampName] = np.array([], dtype=np.float64) | ||
calib.noiseMatrix[ampName] = np.array([], dtype=np.float64) | ||
calib.noiseMatrixNoB[ampName] = np.array([], dtype=np.float64) | ||
|
||
calib.finalVars[ampName] = np.array(dictionary['finalVars'][ampName], dtype=np.float64) | ||
calib.finalModelVars[ampName] = np.array(dictionary['finalModelVars'][ampName], dtype=np.float64) | ||
|
@@ -636,9 +643,6 @@ def _dictOfArraysToDictOfLists(dictOfArrays): | |
outDict['aMatrix'] = _dictOfArraysToDictOfLists(self.aMatrix) | ||
outDict['bMatrix'] = _dictOfArraysToDictOfLists(self.bMatrix) | ||
outDict['noiseMatrix'] = _dictOfArraysToDictOfLists(self.noiseMatrix) | ||
outDict['covariancesModelNoB'] = _dictOfArraysToDictOfLists(self.covariancesModelNoB) | ||
outDict['aMatrixNoB'] = _dictOfArraysToDictOfLists(self.aMatrixNoB) | ||
outDict['noiseMatrixNoB'] = _dictOfArraysToDictOfLists(self.noiseMatrixNoB) | ||
outDict['finalVars'] = _dictOfArraysToDictOfLists(self.finalVars) | ||
outDict['finalModelVars'] = _dictOfArraysToDictOfLists(self.finalModelVars) | ||
outDict['finalMeans'] = _dictOfArraysToDictOfLists(self.finalMeans) | ||
|
@@ -701,15 +705,15 @@ def fromTable(cls, tableList): | |
inDict['aMatrix'] = dict() | ||
inDict['bMatrix'] = dict() | ||
inDict['noiseMatrix'] = dict() | ||
inDict['covariancesModelNoB'] = dict() | ||
inDict['aMatrixNoB'] = dict() | ||
inDict['noiseMatrixNoB'] = dict() | ||
inDict['finalVars'] = dict() | ||
inDict['finalModelVars'] = dict() | ||
inDict['finalMeans'] = dict() | ||
inDict['badAmps'] = [] | ||
inDict['photoCharges'] = dict() | ||
inDict['ampOffsets'] = dict() | ||
inDict['noiseMatrixNoB'] = dict() | ||
inDict['covariancesModelNoB'] = dict() | ||
inDict['aMatrixNoB'] = dict() | ||
|
||
calibVersion = metadata['PTC_VERSION'] | ||
if calibVersion == 1.0: | ||
|
@@ -738,8 +742,6 @@ def fromTable(cls, tableList): | |
inDict['covariancesSqrtWeights'][ampName] = record['COVARIANCES_SQRT_WEIGHTS'] | ||
inDict['aMatrix'][ampName] = record['A_MATRIX'] | ||
inDict['bMatrix'][ampName] = record['B_MATRIX'] | ||
inDict['covariancesModelNoB'][ampName] = record['COVARIANCES_MODEL_NO_B'] | ||
inDict['aMatrixNoB'][ampName] = record['A_MATRIX_NO_B'] | ||
inDict['finalVars'][ampName] = record['FINAL_VARS'] | ||
inDict['finalModelVars'][ampName] = record['FINAL_MODEL_VARS'] | ||
inDict['finalMeans'][ampName] = record['FINAL_MEANS'] | ||
|
@@ -766,9 +768,13 @@ def fromTable(cls, tableList): | |
nanMatrix = np.full_like(inDict['aMatrix'][ampName], np.nan) | ||
inDict['noiseMatrix'][ampName] = nanMatrix | ||
inDict['noiseMatrixNoB'][ampName] = nanMatrix | ||
else: | ||
elif calibVersion >= 1.3 and calibVersion < 2.1: | ||
inDict['noiseMatrix'][ampName] = record['NOISE_MATRIX'] | ||
inDict['noiseMatrixNoB'][ampName] = record['NOISE_MATRIX_NO_B'] | ||
else: | ||
inDict['noiseMatrix'][ampName] = record['NOISE_MATRIX'] | ||
nanMatrix = np.full_like(inDict['aMatrix'][ampName], np.nan) | ||
inDict['noiseMatrixNoB'][ampName] = nanMatrix | ||
if calibVersion < 1.5: | ||
# Matched to `COV_MATRIX_SIDE`. Same for all amps. | ||
inDict['covMatrixSideFullCovFit'] = inDict['covMatrixSide'] | ||
|
@@ -811,6 +817,15 @@ def fromTable(cls, tableList): | |
inDict['ampOffsets'][ampName] = record['AMP_OFFSETS'] | ||
inDict['gainUnadjusted'][ampName] = record['GAIN_UNADJUSTED'] | ||
inDict['gainList'][ampName] = record['GAIN_LIST'] | ||
if calibVersion < 2.1: | ||
inDict['covariancesModelNoB'][ampName] = record['COVARIANCES_MODEL_NO_B'] | ||
inDict['aMatrixNoB'][ampName] = record['A_MATRIX_NO_B'] | ||
else: | ||
inDict['covariancesModelNoB'][ampName] = np.full_like( | ||
inDict['covariancesModel'][ampName], | ||
np.nan, | ||
) | ||
inDict['aMatrixNoB'][ampName] = np.full_like(inDict['aMatrix'][ampName], np.nan) | ||
|
||
inDict['auxValues'] = {} | ||
record = ptcTable[0] | ||
|
@@ -874,16 +889,13 @@ def toTable(self): | |
'PTC_TURNOFF_SAMPLING_ERROR': self.ptcTurnoffSamplingError[ampName], | ||
'A_MATRIX': self.aMatrix[ampName].ravel(), | ||
'B_MATRIX': self.bMatrix[ampName].ravel(), | ||
'A_MATRIX_NO_B': self.aMatrixNoB[ampName].ravel(), | ||
'NOISE_MATRIX': self.noiseMatrix[ampName].ravel(), | ||
'NOISE_MATRIX_NO_B': self.noiseMatrixNoB[ampName].ravel(), | ||
'BAD_AMPS': badAmps, | ||
'PHOTO_CHARGE': self.photoCharges[ampName], | ||
'AMP_OFFSETS': self.ampOffsets[ampName], | ||
'COVARIANCES': self.covariances[ampName].ravel(), | ||
'COVARIANCES_MODEL': self.covariancesModel[ampName].ravel(), | ||
'COVARIANCES_SQRT_WEIGHTS': self.covariancesSqrtWeights[ampName].ravel(), | ||
'COVARIANCES_MODEL_NO_B': self.covariancesModelNoB[ampName].ravel(), | ||
'FINAL_VARS': self.finalVars[ampName], | ||
'FINAL_MODEL_VARS': self.finalModelVars[ampName], | ||
'FINAL_MEANS': self.finalMeans[ampName], | ||
|
@@ -1013,9 +1025,9 @@ def appendPartialPtc(self, partialPtc): | |
self.covMatrixSide, | ||
) | ||
) | ||
self.covariancesModelNoB[ampName] = np.append( | ||
self.covariancesModelNoB[ampName].ravel(), | ||
partialPtc.covariancesModelNoB[ampName].ravel() | ||
self._covariancesModelNoB[ampName] = np.append( | ||
self._covariancesModelNoB[ampName].ravel(), | ||
partialPtc._covariancesModelNoB[ampName].ravel() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We aren't using this at all; I think if we need filler this should be explicitly filled with nans. |
||
).reshape( | ||
( | ||
len(self.rawExpTimes[ampName]), | ||
|
@@ -1072,7 +1084,6 @@ def sort(self, sortIndex): | |
self.covariances[ampName] = self.covariances[ampName][index] | ||
self.covariancesSqrtWeights[ampName] = self.covariancesSqrtWeights[ampName][index] | ||
self.covariancesModel[ampName] = self.covariancesModel[ampName][index] | ||
self.covariancesModelNoB[ampName] = self.covariancesModelNoB[ampName][index] | ||
|
||
self.finalVars[ampName] = self.finalVars[ampName][index] | ||
self.finalModelVars[ampName] = self.finalModelVars[ampName][index] | ||
|
@@ -1174,16 +1185,13 @@ def validateGainNoiseTurnoffValues(self, ampName, doWarn=False): | |
self.noise[ampName] = noise | ||
self.ptcTurnoff[ampName] = ptcTurnoff | ||
|
||
def evalPtcModel(self, mu, setBtoZero=False): | ||
def evalPtcModel(self, mu): | ||
"""Computes the covariance model at specific signal levels. | ||
|
||
Parameters | ||
---------- | ||
mu : `numpy.array`, (N,) | ||
List of mean signals in ADU. | ||
setBtoZero: `bool`, optional | ||
Set "b" parameter in full model (see Astier+19) to zero. | ||
This parameter is ignored if ptcFitType != FULLCOVARIANCE. | ||
|
||
Raises | ||
------ | ||
|
@@ -1212,11 +1220,6 @@ def evalPtcModel(self, mu, setBtoZero=False): | |
of shape (N,), representing an array of [C_{00}] | ||
if self.ptcFitType == EXPAPPROXIMATION or | ||
self.ptcFitType == POLYNOMAIL. | ||
|
||
Note that the PhotoTransferCurveDataset does not store | ||
the gain fit parameter for FULLCOVARIANCE with b=0, so | ||
we can't recompute the covariance model with | ||
setBtoZero=True exactly. | ||
""" | ||
|
||
ampNames = self.ampNames | ||
|
@@ -1239,22 +1242,14 @@ def evalPtcModel(self, mu, setBtoZero=False): | |
c00 = f1 + f2 | ||
covModel[ampName] = c00 | ||
|
||
elif self.ptcFitType == "FULLCOVARIANCE": | ||
elif self.ptcFitType in ["FULLCOVARIANCE", "FULLCOVARIANCE_NO_B"]: | ||
for ampName in ampNames: | ||
noiseMatrix = self.noiseMatrix[ampName] | ||
gain = self.gain[ampName] | ||
aMatrix = self.aMatrix[ampName] | ||
bMatrix = self.bMatrix[ampName] | ||
cMatrix = aMatrix*bMatrix | ||
|
||
if setBtoZero: | ||
# Note that the PhotoTransferCurveDataset does not store | ||
# the gain fit parameter for FULLCOVARIANCE with b=0, so | ||
# we can't recompute the covariance model with | ||
# setBtoZero=True. | ||
raise NotImplementedError("Cannot evaulate the PTC model" | ||
"with b=0 for FULLCOVARIANCE.") | ||
|
||
matrixSideFit = self.covMatrixSideFullCovFit | ||
sa = (matrixSideFit, matrixSideFit) | ||
|
||
|
@@ -1283,9 +1278,6 @@ def evalPtcModel(self, mu, setBtoZero=False): | |
bigMu = mu[:, np.newaxis, np.newaxis]*gain | ||
# c(=a*b in Astier+19) also has a contribution to the last | ||
# term, that is absent for now. | ||
if setBtoZero: | ||
c1 = np.zeros_like(c1) | ||
ac = np.zeros_like(ac) | ||
|
||
covModel[ampName] = (bigMu/(gain*gain)*(a1*bigMu+2./3.*(bigMu*bigMu)*(a2 + c1) | ||
+ (1./3.*a3 + 5./6.*ac)*(bigMu*bigMu*bigMu)) | ||
|
@@ -1306,3 +1298,33 @@ def _validateCovarianceMatrizSizes(self): | |
f"({self.covMatrixSideFullCovFit} > {self.covMatrixSide})." | ||
"Setting the former to the latter.") | ||
self.covMatrixSideFullCovFit = self.covMatrixSide | ||
|
||
@property | ||
@deprecated( | ||
reason="The covariancesModelNoB attribute is deprecated. Will be " | ||
"removed after v28.", | ||
version="v28.0", | ||
category=FutureWarning | ||
) | ||
def covariancesModelNoB(self): | ||
return self._covariancesModelNoB | ||
|
||
@property | ||
@deprecated( | ||
reason="The aMatrixNoB attribute is deprecated. Will be " | ||
"removed after v28.", | ||
version="v28.0", | ||
category=FutureWarning | ||
) | ||
def aMatrixNoB(self): | ||
return self._aMatrixNoB | ||
|
||
@property | ||
@deprecated( | ||
reason="The noiseMatrixNoB attribute is deprecated. Will be " | ||
"removed after v28.", | ||
version="v28.0", | ||
category=FutureWarning | ||
) | ||
def noiseMatrixNoB(self): | ||
return self._noiseMatrixNoB |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know if this else is necessary. For newer PTCs we shouldn't be using these fields at all, right?