24 from scipy.signal
import fftconvolve
25 from scipy.optimize
import leastsq
26 from .astierCovFitParameters
import FitParameters
34 """Make covariances array from tuple.
38 inputTuple: `numpy.ndarray`
39 Structured array with rows with at least
40 (mu, afwVar, cov, var, i, j, npix), where:
42 mu : 0.5*(m1 + m2), where:
43 mu1: mean value of flat1
44 mu2: mean value of flat2
45 afwVar: variance of difference flat, calculated with afw
46 cov: covariance value at lag(i, j)
47 var: variance(covariance value at lag(0, 0))
50 npix: number of pixels used for covariance calculation.
52 maxRangeFromTuple: `int`
53 Maximum range to select from tuple.
58 Covariance arrays, indexed by mean signal mu.
61 Variance arrays, indexed by mean signal mu.
64 List of mean signal values.
69 The input tuple should contain the following rows:
70 (mu, cov, var, i, j, npix), with one entry per lag, and image pair.
71 Different lags(i.e. different i and j) from the same
72 image pair have the same values of mu1 and mu2. When i==j==0, cov
75 If the input tuple contains several video channels, one should
76 select the data of a given channel *before* entering this
77 routine, as well as apply(e.g.) saturation cuts.
79 The routine returns cov[k_mu, j, i], vcov[(same indices)], and mu[k]
80 where the first index of cov matches the one in mu.
82 This routine implements the loss of variance due to
83 clipping cuts when measuring variances and covariance, but this should happen inside
84 the measurement code, where the cuts are readily available.
87 if maxRangeFromTuple
is not None:
88 cut = (inputTuple[
'i'] < maxRangeFromTuple) & (inputTuple[
'j'] < maxRangeFromTuple)
89 cutTuple = inputTuple[cut]
93 muTemp = cutTuple[
'mu']
94 ind = np.argsort(muTemp)
96 cutTuple = cutTuple[ind]
99 xx = np.hstack(([mu[0]], mu))
100 delta = xx[1:] - xx[:-1]
101 steps, = np.where(delta > 0)
102 ind = np.zeros_like(mu, dtype=int)
106 muVals = np.array(np.unique(mu))
107 i = cutTuple[
'i'].astype(int)
108 j = cutTuple[
'j'].astype(int)
109 c = 0.5*cutTuple[
'cov']
111 v = 0.5*cutTuple[
'var']
113 cov = np.ndarray((len(muVals), np.max(i)+1, np.max(j)+1))
114 var = np.zeros_like(cov)
116 var[ind, i, j] = v**2/n
125 return cov, var, muVals
129 """ Copy array over 4 quadrants prior to convolution.
133 inputarray: `numpy.array`
134 Input array to symmetrize.
142 targetShape =
list(inputArray.shape)
143 r1, r2 = inputArray.shape[-1], inputArray.shape[-2]
144 targetShape[-1] = 2*r1-1
145 targetShape[-2] = 2*r2-1
146 aSym = np.ndarray(tuple(targetShape))
147 aSym[..., r2-1:, r1-1:] = inputArray
148 aSym[..., r2-1:, r1-1::-1] = inputArray
149 aSym[..., r2-1::-1, r1-1::-1] = inputArray
150 aSym[..., r2-1::-1, r1-1:] = inputArray
156 """A class to fit the models in Astier+19 to flat covariances.
158 This code implements the model(and the fit thereof) described in
159 Astier+19: https://arxiv.org/pdf/1905.08677.pdf
163 meanSignals : `list`[`float`]
164 List with means of the difference image of two flats,
165 for a particular amplifier in the detector.
167 covariances : `list`[`numpy.array`]
168 List with 2D covariance arrays at a given mean signal.
170 covsSqrtWeights : `list`[`numpy.array`]
171 List with 2D arrays with weights from `vcov as defined in
172 `makeCovArray`: weight = 1/sqrt(vcov).
174 maxRangeFromTuple: `int`, optional
175 Maximum range to select from tuple.
177 meanSignalMask: `list`[`bool`], optional
178 Mask of mean signal 1D array. Use all entries if empty.
181 def __init__(self, meanSignals, covariances, covsSqrtWeights, maxRangeFromTuple=8, meanSignalsMask=[]):
182 assert (len(meanSignals) == len(covariances))
183 assert (len(covariances) == len(covsSqrtWeights))
184 if len(meanSignalsMask) == 0:
185 meanSignalsMask = np.repeat(
True, len(meanSignals))
186 self.
mumu = meanSignals[meanSignalsMask]
187 self.
covcov = np.nan_to_num(covariances)[meanSignalsMask]
189 self.
sqrtWsqrtW = np.nan_to_num(covsSqrtWeights)[meanSignalsMask]
190 self.
rr = maxRangeFromTuple
191 self.
loggerlogger = lsstLog.Log.getDefaultLogger()
194 """Make a copy of params"""
195 cop = copy.deepcopy(self)
197 if hasattr(self,
'params'):
202 """ Performs a crude parabolic fit of the data in order to start
203 the full fit close to the solution.
206 lenA = self.
rr*self.
rr
209 self.
paramsparams[
'gain'] = 1.
211 self.
paramsparams[
'c'].fix(val=0.)
213 a = self.
paramsparams[
'a'].full.reshape(self.
rr, self.
rr)
214 noise = self.
paramsparams[
'noise'].full.reshape(self.
rr, self.
rr)
215 gain = self.
paramsparams[
'gain'].full[0]
224 for i
in range(self.
rr):
225 for j
in range(self.
rr):
227 parsFit = np.polyfit(self.
mumu, self.
covcov[:, i, j] - model[:, i, j],
228 2, w=self.
sqrtWsqrtW[:, i, j])
230 a[i, j] += parsFit[0]
231 noise[i, j] += parsFit[2]*gain*gain
233 gain = 1./(1/gain+parsFit[1])
234 self.
paramsparams[
'gain'].full[0] = gain
235 chi2 = self.
chi2chi2()
243 """Return an array of free parameter values (it is a copy)."""
244 return self.
paramsparams.free + 0.
247 """Set parameter values."""
248 self.
paramsparams.free = p
252 """Computes full covariances model (Eq. 20 of Astier+19).
256 mu: `numpy.array`, optional
257 List of mean signals.
261 covModel: `numpy.array`
266 By default, computes the covModel for the mu's stored(self.mu).
268 Returns cov[Nmu, self.r, self.r]. The variance for the PTC is cov[:, 0, 0].
269 mu and cov are in ADUs and ADUs squared. To use electrons for both,
270 the gain should be set to 1. This routine implements the model in Astier+19 (1905.08677).
272 The parameters of the full model for C_ij(mu) ("C_ij" and "mu" in ADU^2 and ADU, respectively)
273 in Astier+19 (Eq. 20) are:
275 "a" coefficients (r by r matrix), units: 1/e
276 "b" coefficients (r by r matrix), units: 1/e
277 noise matrix (r by r matrix), units: e^2
280 "b" appears in Eq. 20 only through the "ab" combination, which is defined in this code as "c=ab".
282 sa = (self.
rr, self.
rr)
283 a = self.
paramsparams[
'a'].full.reshape(sa)
284 c = self.
paramsparams[
'c'].full.reshape(sa)
285 gain = self.
paramsparams[
'gain'].full[0]
286 noise = self.
paramsparams[
'noise'].full.reshape(sa)
288 aEnlarged = np.zeros((int(sa[0]*1.5)+1, int(sa[1]*1.5)+1))
289 aEnlarged[0:sa[0], 0:sa[1]] = a
292 cEnlarged = np.zeros((int(sa[0]*1.5)+1, int(sa[1]*1.5)+1))
293 cEnlarged[0:sa[0], 0:sa[1]] = c
295 a2 = fftconvolve(aSym, aSym, mode=
'same')
296 a3 = fftconvolve(a2, aSym, mode=
'same')
297 ac = fftconvolve(aSym, cSym, mode=
'same')
298 (xc, yc) = np.unravel_index(np.abs(aSym).argmax(), a2.shape)
300 a1 = a[np.newaxis, :, :]
301 a2 = a2[np.newaxis, xc:xc + range, yc:yc + range]
302 a3 = a3[np.newaxis, xc:xc + range, yc:yc + range]
303 ac = ac[np.newaxis, xc:xc + range, yc:yc + range]
304 c1 = c[np.newaxis, ::]
308 bigMu = mu[:, np.newaxis, np.newaxis]*gain
310 covModel = (bigMu/(gain*gain)*(a1*bigMu+2./3.*(bigMu*bigMu)*(a2 + c1)
311 + (1./3.*a3 + 5./6.*ac)*(bigMu*bigMu*bigMu)) + noise[np.newaxis, :, :]/gain**2)
313 covModel[:, 0, 0] += mu/gain
318 """'a' matrix from Astier+19(e.g., Eq. 20)"""
319 return self.
paramsparams[
'a'].full.reshape(self.
rr, self.
rr)
322 """'b' matrix from Astier+19(e.g., Eq. 20)"""
323 return self.
paramsparams[
'c'].full.reshape(self.
rr, self.
rr)/self.
getAgetA()
326 """'c'='ab' matrix from Astier+19(e.g., Eq. 20)"""
327 return np.array(self.
paramsparams[
'c'].full.reshape(self.
rr, self.
rr))
329 def _getCovParams(self, what):
330 """Get covariance matrix of parameters from fit"""
331 indices = self.
paramsparams[what].indexof()
332 i1 = indices[:, np.newaxis]
333 i2 = indices[np.newaxis, :]
341 """Get covariance matrix of "a" coefficients from fit"""
343 cova = self.
_getCovParams_getCovParams(
'a').reshape((self.
rr, self.
rr, self.
rr, self.
rr))
349 """Square root of diagonal of the parameter covariance of the fitted "a" matrix"""
351 sigA = np.sqrt(self.
_getCovParams_getCovParams(
'a').diagonal()).reshape((self.
rr, self.
rr))
357 """Get covariance matrix of "a" coefficients from fit
361 aval = self.
getAgetA().flatten()
362 factor = np.outer(aval, aval)
364 return covb.reshape((self.
rr, self.
rr, self.
rr, self.
rr))
367 """Get covariance matrix of "c" coefficients from fit"""
369 return cova.reshape((self.
rr, self.
rr, self.
rr, self.
rr))
372 """Get error on fitted gain parameter"""
374 gainErr = np.sqrt(self.
_getCovParams_getCovParams(
'gain')[0][0])
380 """Get covariances of noise matrix from fit"""
382 return covNoise.reshape((self.
rr, self.
rr, self.
rr, self.
rr))
385 """Square root of diagonal of the parameter covariance of the fitted "noise" matrix"""
388 noise = np.sqrt(covNoise.diagonal()).reshape((self.
rr, self.
rr))
394 """Get gain (e/ADU)"""
395 return self.
paramsparams[
'gain'].full[0]
398 """Get readout noise (e^2)"""
399 return self.
paramsparams[
'noise'].full[0]
402 """Get error on readout noise parameter"""
403 ronSqrt = np.sqrt(np.fabs(self.
getRongetRon()))
406 ronErr = 0.5*(noiseSigma/np.fabs(self.
getRongetRon()))*ronSqrt
412 """Get noise matrix"""
413 return self.
paramsparams[
'noise'].full.reshape(self.
rr, self.
rr)
416 """Get mask of Cov[i,j]"""
417 weights = self.
sqrtWsqrtW[:, i, j]
422 """Set "a" and "b" coeffcients forfull Astier+19 model (Eq. 20). "c=a*b"."""
423 self.
paramsparams[
'a'].full = a.flatten()
424 self.
paramsparams[
'c'].full = a.flatten()*b.flatten()
428 """Calculate weighted chi2 of full-model fit."""
432 """Weighted residuals.
437 c = CovFit(meanSignals, covariances, covsSqrtWeights)
439 coeffs, cov, _, mesg, ierr = leastsq(c.weightedRes, c.getParamValues(), full_output=True)
441 if params
is not None:
443 covModel = np.nan_to_num(self.
evalCovModelevalCovModel())
444 weightedRes = (covModel-self.
covcov)*self.
sqrtWsqrtW
446 return weightedRes.flatten()
449 """Fit measured covariances to full model in Astier+19 (Eq. 20)
454 Initial parameters of the fit.
455 len(pInit) = #entries(a) + #entries(c) + #entries(noise) + 1
456 len(pInit) = r^2 + r^2 + r^2 + 1, where "r" is the maximum lag considered for the
457 covariances calculation, and the extra "1" is the gain.
458 If "b" is 0, then "c" is 0, and len(pInit) will have r^2 fewer entries.
463 Fit parameters (see "Notes" below).
467 The parameters of the full model for C_ij(mu) ("C_ij" and "mu" in ADU^2 and ADU, respectively)
468 in Astier+19 (Eq. 20) are:
470 "a" coefficients (r by r matrix), units: 1/e
471 "b" coefficients (r by r matrix), units: 1/e
472 noise matrix (r by r matrix), units: e^2
475 "b" appears in Eq. 20 only through the "ab" combination, which is defined in this code as "c=ab".
480 params, paramsCov, _, mesg, ierr = leastsq(self.
weightedResweightedRes, pInit, full_output=
True)
485 def getFitData(self, i, j, divideByMu=False, unitsElectrons=False, returnMasked=False):
486 """Get measured signal and covariance, cov model, weigths, and mask at covariance lag (i, j).
491 Lag for covariance matrix.
494 Lag for covariance matrix.
496 divideByMu: `bool`, optional
497 Divide covariance, model, and weights by signal mu?
499 unitsElectrons : `bool`, optional
500 mu, covariance, and model are in ADU (or powers of ADU) If tthis parameter is true, these are
501 multiplied by the adequte factors of the gain to return quantities in electrons
502 (or powers of electrons).
504 returnMasked : `bool`, optional
505 Use mask (based on weights) in returned arrays (mu, covariance, and model)?
510 list of signal values (mu).
512 covariance: `numpy.array`
513 Covariance arrays, indexed by mean signal mu (self.cov[:, i, j]).
515 covarianceModel: `numpy.array`
516 Covariance model (model).
518 weights: `numpy.array`
521 mask : `numpy.array`, optional
522 Boolean mask of the covariance at (i,j).
526 Using a CovFit object, selects from (i, j) and returns
527 mu*gain, self.cov[:, i, j]*gain**2 model*gain**2, and self.sqrtW/gain**2
528 in electrons or ADU if unitsElectrons=False.
536 covariance = self.
covcov[:, i, j]*(gain**2)
537 covarianceModel = self.
evalCovModelevalCovModel()[:, i, j]*(gain**2)
538 weights = self.
sqrtWsqrtW[:, i, j]/(gain**2)
543 weights = weights[mask]
544 covarianceModel = covarianceModel[mask]
546 covariance = covariance[mask]
550 covarianceModel /= mu
553 return mu, covariance, covarianceModel, weights, mask
def _getCovParams(self, what)
def weightedRes(self, params=None)
def evalCovModel(self, mu=None)
def getMaskCov(self, i, j)
def setParamValues(self, p)
def getFitData(self, i, j, divideByMu=False, unitsElectrons=False, returnMasked=False)
def fitFullModel(self, pInit=None)
def __init__(self, meanSignals, covariances, covsSqrtWeights, maxRangeFromTuple=8, meanSignalsMask=[])
daf::base::PropertyList * list
def symmetrize(inputArray)
def makeCovArray(inputTuple, maxRangeFromTuple=8)