23 __all__ = [
'MeasurePhotonTransferCurveTask',
24 'MeasurePhotonTransferCurveTaskConfig', ]
27 import matplotlib.pyplot
as plt
29 from matplotlib.backends.backend_pdf
import PdfPages
30 from sqlite3
import OperationalError
33 import lsst.pex.config
as pexConfig
36 from .utils
import (NonexistentDatasetTaskDataIdContainer, PairedVisitListTaskRunner,
37 checkExpLengthEqual, validateIsrConfig)
38 from scipy.optimize
import leastsq
39 import numpy.polynomial.polynomial
as poly
43 """Config class for photon transfer curve measurement task""" 44 isr = pexConfig.ConfigurableField(
46 doc=
"""Task to perform instrumental signature removal.""",
48 isrMandatorySteps = pexConfig.ListField(
50 doc=
"isr operations that must be performed for valid results. Raises if any of these are False.",
51 default=[
'doAssembleCcd']
53 isrForbiddenSteps = pexConfig.ListField(
55 doc=
"isr operations that must NOT be performed for valid results. Raises if any of these are True",
56 default=[
'doFlat',
'doFringe',
'doAddDistortionModel',
'doBrighterFatter',
'doUseOpticsTransmission',
57 'doUseFilterTransmission',
'doUseSensorTransmission',
'doUseAtmosphereTransmission']
59 isrDesirableSteps = pexConfig.ListField(
61 doc=
"isr operations that it is advisable to perform, but are not mission-critical." +
62 " WARNs are logged for any of these found to be False.",
63 default=[
'doBias',
'doDark',
'doCrosstalk',
'doDefect']
65 isrUndesirableSteps = pexConfig.ListField(
67 doc=
"isr operations that it is *not* advisable to perform in the general case, but are not" +
68 " forbidden as some use-cases might warrant them." +
69 " WARNs are logged for any of these found to be True.",
70 default=[
'doLinearize']
72 ccdKey = pexConfig.Field(
74 doc=
"The key by which to pull a detector from a dataId, e.g. 'ccd' or 'detector'.",
77 makePlots = pexConfig.Field(
79 doc=
"Plot the PTC curves?.",
82 ptcFitType = pexConfig.ChoiceField(
84 doc=
"Fit PTC to approximation in Astier+19 (Equation 16) or to a polynomial.",
87 "POLYNOMIAL":
"n-degree polynomial (use 'polynomialFitDegree' to set 'n').",
88 "ASTIERAPPROXIMATION":
"Approximation in Astier+19 (Eq. 16)." 91 polynomialFitDegree = pexConfig.Field(
93 doc=
"Degree of polynomial to fit the PTC, when 'ptcFitType'=POLYNOMIAL.",
96 binSize = pexConfig.Field(
98 doc=
"Bin the image by this factor in both dimensions.",
101 minMeanSignal = pexConfig.Field(
103 doc=
"Minimum value of mean signal (in ADU) to consider.",
106 maxMeanSignal = pexConfig.Field(
108 doc=
"Maximum value to of mean signal (in ADU) to consider.",
111 sigmaCutPtcOutliers = pexConfig.Field(
113 doc=
"Sigma cut for outlier rejection in PTC.",
116 maxIterationsPtcOutliers = pexConfig.Field(
118 doc=
"Maximum number of iterations for outlier rejection in PTC.",
121 doFitBootstrap = pexConfig.Field(
123 doc=
"Use bootstrap for the PTC fit parameters and errors?.",
126 linResidualTimeIndex = pexConfig.Field(
128 doc=
"Index position in time array for reference time in linearity residual calculation.",
134 """A class to calculate, fit, and plot a PTC from a set of flat pairs. 136 The Photon Transfer Curve (var(signal) vs mean(signal)) is a standard tool 137 used in astronomical detectors characterization (e.g., Janesick 2001, 138 Janesick 2007). This task calculates the PTC from a series of pairs of 139 flat-field images; each pair taken at identical exposure times. The 140 difference image of each pair is formed to eliminate fixed pattern noise, 141 and then the variance of the difference image and the mean of the average image 142 are used to produce the PTC. An n-degree polynomial or the approximation in Equation 143 16 of Astier+19 ("The Shape of the Photon Transfer Curve of CCD sensors", 144 arXiv:1905.08677) can be fitted to the PTC curve. These models include 145 parameters such as the gain (e/ADU) and readout noise. 151 Positional arguments passed to the Task constructor. None used at this 154 Keyword arguments passed on to the Task constructor. None used at this 159 RunnerClass = PairedVisitListTaskRunner
160 ConfigClass = MeasurePhotonTransferCurveTaskConfig
161 _DefaultName =
"measurePhotonTransferCurve" 164 pipeBase.CmdLineTask.__init__(self, *args, **kwargs)
165 self.makeSubtask(
"isr")
166 plt.interactive(
False)
168 self.config.isrForbiddenSteps, self.config.isrDesirableSteps, checkTrim=
False)
169 self.config.validate()
173 def _makeArgumentParser(cls):
174 """Augment argument parser for the MeasurePhotonTransferCurveTask.""" 176 parser.add_argument(
"--visit-pairs", dest=
"visitPairs", nargs=
"*",
177 help=
"Visit pairs to use. Each pair must be of the form INT,INT e.g. 123,456")
178 parser.add_id_argument(
"--id", datasetType=
"measurePhotonTransferCurveGainAndNoise",
179 ContainerClass=NonexistentDatasetTaskDataIdContainer,
180 help=
"The ccds to use, e.g. --id ccd=0..100")
185 """Run the Photon Transfer Curve (PTC) measurement task. 187 For a dataRef (which is each detector here), 188 and given a list of visit pairs at different exposure times, 193 dataRef : list of lsst.daf.persistence.ButlerDataRef 194 dataRef for the detector for the visits to be fit. 195 visitPairs : `iterable` of `tuple` of `int` 196 Pairs of visit numbers to be processed together 200 detNum = dataRef.dataId[self.config.ccdKey]
201 detector = dataRef.get(
'camera')[dataRef.dataId[self.config.ccdKey]]
207 for name
in dataRef.getButler().getKeys(
'bias'):
208 if name
not in dataRef.dataId:
210 dataRef.dataId[name] = \
211 dataRef.getButler().queryMetadata(
'raw', [name], detector=detNum)[0]
212 except OperationalError:
215 amps = detector.getAmplifiers()
216 ampNames = [amp.getName()
for amp
in amps]
217 dataDict = {key: {}
for key
in ampNames}
218 fitVectorsDict = {key: ([], [], [])
for key
in ampNames}
220 self.log.info(
'Measuring PTC using %s visits for detector %s' % (visitPairs, detNum))
222 for (v1, v2)
in visitPairs:
224 dataRef.dataId[
'visit'] = v1
226 dataRef.dataId[
'visit'] = v2
228 del dataRef.dataId[
'visit']
231 expTime = exp1.getInfo().getVisitInfo().getExposureTime()
235 data = dict(expTime=expTime, meanClip=mu, varClip=varDiff)
236 ampName = amp.getName()
237 dataDict[ampName][(v1, v2)] = data
238 fitVectorsDict[ampName][0].append(expTime)
239 fitVectorsDict[ampName][1].append(mu)
240 fitVectorsDict[ampName][2].append(varDiff)
243 fitPtcDict, nlDict, gainDict, noiseDict = self.
fitPtcAndNl(fitVectorsDict,
244 ptcFitType=self.config.ptcFitType)
245 allDict = {
"data": dataDict,
"ptc": fitPtcDict,
"nl": nlDict}
246 gainNoiseNlDict = {
"gain": gainDict,
"noise": noiseDict,
"nl": nlDict}
248 if self.config.makePlots:
249 self.
plot(dataRef, fitPtcDict, nlDict, ptcFitType=self.config.ptcFitType)
252 self.log.info(f
"Writing PTC and NL data to {dataRef.getUri(write=True)}")
253 dataRef.put(gainNoiseNlDict, datasetType=
"measurePhotonTransferCurveGainAndNoise")
254 dataRef.put(allDict, datasetType=
"measurePhotonTransferCurveDatasetAll")
256 self.log.info(
'Finished measuring PTC for in detector %s' % detNum)
258 return pipeBase.Struct(exitStatus=0)
261 """Calculate the mean signal of two exposures and the variance of their difference. 265 exposure1 : `lsst.afw.image.exposure.exposure.ExposureF` 266 First exposure of flat field pair. 268 exposure2 : `lsst.afw.image.exposure.exposure.ExposureF` 269 Second exposure of flat field pair. 271 region : `lsst.geom.Box2I` 272 Region of each exposure where to perform the calculations (e.g, an amplifier). 278 0.5*(mu1 + mu2), where mu1, and mu2 are the clipped means of the regions in 282 Half of the clipped variance of the difference of the regions inthe two input 286 if region
is not None:
287 im1Area = exposure1.maskedImage[region]
288 im2Area = exposure2.maskedImage[region]
290 im1Area = exposure1.maskedImage
291 im2Area = exposure2.maskedImage
293 im1Area = afwMath.binImage(im1Area, self.config.binSize)
294 im2Area = afwMath.binImage(im2Area, self.config.binSize)
297 mu1 = afwMath.makeStatistics(im1Area, afwMath.MEANCLIP).getValue()
298 mu2 = afwMath.makeStatistics(im2Area, afwMath.MEANCLIP).getValue()
303 temp = im2Area.clone()
305 diffIm = im1Area.clone()
310 varDiff = 0.5*(afwMath.makeStatistics(diffIm, afwMath.VARIANCECLIP).getValue())
314 def _fitLeastSq(self, initialParams, dataX, dataY, function):
315 """Do a fit and estimate the parameter errors using using scipy.optimize.leastq. 317 optimize.leastsq returns the fractional covariance matrix. To estimate the 318 standard deviation of the fit parameters, multiply the entries of this matrix 319 by the reduced chi squared and take the square root of the diagon al elements. 323 initialParams : list of np.float 324 initial values for fit parameters. For ptcFitType=POLYNOMIAL, its length 325 determines the degree of the polynomial. 327 dataX : np.array of np.float 328 Data in the abscissa axis. 330 dataY : np.array of np.float 331 Data in the ordinate axis. 333 function : callable object (function) 334 Function to fit the data with. 338 pFitSingleLeastSquares : list of np.float 339 List with fitted parameters. 341 pErrSingleLeastSquares : list of np.float 342 List with errors for fitted parameters. 345 def errFunc(p, x, y):
346 return function(p, x) - y
348 pFit, pCov, infoDict, errMessage, success = leastsq(errFunc, initialParams,
349 args=(dataX, dataY), full_output=1, epsfcn=0.0001)
351 if (len(dataY) > len(initialParams))
and pCov
is not None:
352 reducedChiSq = (errFunc(pFit, dataX, dataY)**2).sum()/(len(dataY)-len(initialParams))
358 for i
in range(len(pFit)):
359 errorVec.append(np.fabs(pCov[i][i])**0.5)
361 pFitSingleLeastSquares = pFit
362 pErrSingleLeastSquares = np.array(errorVec)
364 return pFitSingleLeastSquares, pErrSingleLeastSquares
366 def _fitBootstrap(self, initialParams, dataX, dataY, function, confidenceSigma=1.):
367 """Do a fit using least squares and bootstrap to estimate parameter errors. 369 The bootstrap error bars are calculated by fitting 100 random data sets. 373 initialParams : list of np.float 374 initial values for fit parameters. For ptcFitType=POLYNOMIAL, its length 375 determines the degree of the polynomial. 377 dataX : np.array of np.float 378 Data in the abscissa axis. 380 dataY : np.array of np.float 381 Data in the ordinate axis. 383 function : callable object (function) 384 Function to fit the data with. 386 confidenceSigma : np.float 387 Number of sigmas that determine confidence interval for the bootstrap errors. 391 pFitBootstrap : list of np.float 392 List with fitted parameters. 394 pErrBootstrap : list of np.float 395 List with errors for fitted parameters. 398 def errFunc(p, x, y):
399 return function(p, x) - y
402 pFit, _ = leastsq(errFunc, initialParams, args=(dataX, dataY), full_output=0)
405 residuals = errFunc(pFit, dataX, dataY)
406 sigmaErrTotal = np.std(residuals)
411 randomDelta = np.random.normal(0., sigmaErrTotal, len(dataY))
412 randomDataY = dataY + randomDelta
413 randomFit, _ = leastsq(errFunc, initialParams,
414 args=(dataX, randomDataY), full_output=0)
415 pars.append(randomFit)
416 pars = np.array(pars)
417 meanPfit = np.mean(pars, 0)
420 nSigma = confidenceSigma
421 errPfit = nSigma*np.std(pars, 0)
422 pFitBootstrap = meanPfit
423 pErrBootstrap = errPfit
424 return pFitBootstrap, pErrBootstrap
427 """Polynomial function definition""" 428 return poly.polyval(x, [*pars])
431 """Single brighter-fatter parameter model for PTC; Equation 16 of Astier+19""" 432 a00, gain, noise = pars
433 return 0.5/(a00*gain*gain)*(np.exp(2*a00*x*gain)-1) + noise/(gain*gain)
436 """Function to fit PTC, and calculate linearity and linearity residual 440 fitVectorsDicti : `dict` 441 Dictionary with exposure time, mean, and variance vectors in a tuple 443 Fit a 'POLYNOMIAL' (degree: 'polynomialFitDegree') or ' 444 ASTIERAPPROXIMATION' to the PTC 449 Dictionary of the form fitPtcDict[amp] = 450 (meanVec, varVec, parsFit, parsFitErr, index) 452 Dictionary of the form nlDict[amp] = 453 (timeVec, meanVec, linResidual, parsFit, parsFitErr) 455 if ptcFitType ==
'ASTIERAPPROXIMATION':
457 parsIniPtc = [-1e-9, 1.0, 10.]
458 if ptcFitType ==
'POLYNOMIAL':
460 parsIniPtc = np.repeat(1., self.config.polynomialFitDegree + 1)
462 parsIniNl = [1., 1., 1.]
463 fitPtcDict = {key: {}
for key
in fitVectorsDict}
464 nlDict = {key: {}
for key
in fitVectorsDict}
465 gainDict = {key: {}
for key
in fitVectorsDict}
466 noiseDict = {key: {}
for key
in fitVectorsDict}
468 def errFunc(p, x, y):
469 return ptcFunc(p, x) - y
471 maxIterationsPtcOutliers = self.config.maxIterationsPtcOutliers
472 for amp
in fitVectorsDict:
473 timeVec, meanVec, varVec = fitVectorsDict[amp]
474 timeVecOriginal = np.array(timeVec)
475 meanVecOriginal = np.array(meanVec)
476 varVecOriginal = np.array(varVec)
477 index0 = ((meanVecOriginal > self.config.minMeanSignal) &
478 (meanVecOriginal <= self.config.maxMeanSignal))
481 sigmaCutPtcOutliers = self.config.sigmaCutPtcOutliers
482 maxIterationsPtcOutliers = self.config.maxIterationsPtcOutliers
483 timeTempVec = timeVecOriginal[index0]
484 meanTempVec = meanVecOriginal[index0]
485 varTempVec = varVecOriginal[index0]
486 while count <= maxIterationsPtcOutliers:
487 pars, cov = leastsq(errFunc, parsIniPtc, args=(meanTempVec,
488 varTempVec), full_output=0)
489 sigResids = (varTempVec -
490 ptcFunc(pars, meanTempVec))/np.sqrt(varTempVec)
491 index = list(np.where(np.abs(sigResids) < sigmaCutPtcOutliers)[0])
492 timeTempVec = timeTempVec[index]
493 meanTempVec = meanTempVec[index]
494 varTempVec = varTempVec[index]
498 timeVecFinal, meanVecFinal, varVecFinal = timeTempVec, meanTempVec, varTempVec
499 if (len(meanVecFinal) - len(meanVecOriginal)) > 0:
500 self.log.info((f
"Number of points discarded in PTC of amplifier {amp}:" +
501 "{len(meanVecFinal)-len(meanVecOriginal)} out of {len(meanVecOriginal)}"))
503 if (len(meanVecFinal) < len(parsIniPtc)):
504 raise RuntimeError(f
"Not enough data points ({len(meanVecFinal)}) compared to the number of" +
505 "parameters of the PTC model({len(parsIniPtc)}).")
507 if self.config.doFitBootstrap:
508 parsFit, parsFitErr = self.
_fitBootstrap(parsIniPtc, meanVecFinal, varVecFinal, ptcFunc)
510 parsFit, parsFitErr = self.
_fitLeastSq(parsIniPtc, meanVecFinal, varVecFinal, ptcFunc)
512 fitPtcDict[amp] = (timeVecOriginal, meanVecOriginal, varVecOriginal, timeVecFinal,
513 meanVecFinal, varVecFinal, parsFit, parsFitErr)
515 if ptcFitType ==
'ASTIERAPPROXIMATION':
517 ptcGainErr = parsFitErr[1]
518 ptcNoise = np.sqrt(np.fabs(parsFit[2]))
519 ptcNoiseErr = 0.5*(parsFitErr[2]/np.fabs(parsFit[2]))*np.sqrt(np.fabs(parsFit[2]))
520 if ptcFitType ==
'POLYNOMIAL':
521 ptcGain = 1./parsFit[1]
522 ptcGainErr = np.fabs(1./parsFit[1])*(parsFitErr[1]/parsFit[1])
523 ptcNoise = np.sqrt(np.fabs(parsFit[0]))*ptcGain
524 ptcNoiseErr = (0.5*(parsFitErr[0]/np.fabs(parsFit[0]))*(np.sqrt(np.fabs(parsFit[0]))))*ptcGain
526 gainDict[amp] = (ptcGain, ptcGainErr)
527 noiseDict[amp] = (ptcNoise, ptcNoiseErr)
531 if self.config.doFitBootstrap:
532 parsFit, parsFitErr = self.
_fitBootstrap(parsIniNl, timeVecFinal, meanVecFinal,
535 parsFit, parsFitErr = self.
_fitLeastSq(parsIniNl, timeVecFinal, meanVecFinal,
537 linResidualTimeIndex = self.config.linResidualTimeIndex
538 if timeVecFinal[linResidualTimeIndex] == 0.0:
539 raise RuntimeError(
"Reference time for linearity residual can't be 0.0")
540 linResidual = 100*(1 - ((meanVecFinal[linResidualTimeIndex] /
541 timeVecFinal[linResidualTimeIndex]) / (meanVecFinal/timeVecFinal)))
542 nlDict[amp] = (timeVecFinal, meanVecFinal, linResidual, parsFit, parsFitErr)
544 return fitPtcDict, nlDict, gainDict, noiseDict
546 def plot(self, dataRef, fitPtcDict, nlDict, ptcFitType='POLYNOMIAL'):
547 dirname = dataRef.getUri(datasetType=
'cpPipePlotRoot', write=
True)
548 if not os.path.exists(dirname):
551 detNum = dataRef.dataId[self.config.ccdKey]
552 filename = f
"PTC_det{detNum}.pdf" 553 filenameFull = os.path.join(dirname, filename)
554 with PdfPages(filenameFull)
as pdfPages:
555 self.
_plotPtc(fitPtcDict, nlDict, ptcFitType, pdfPages)
557 def _plotPtc(self, fitPtcDict, nlDict, ptcFitType, pdfPages):
558 """Plot PTC, linearity, and linearity residual per amplifier""" 560 if ptcFitType ==
'ASTIERAPPROXIMATION':
562 stringTitle =
r"Var = $\frac{1}{2g^2a_{00}}(\exp (2a_{00} \mu g) - 1) + \frac{n_{00}}{g^2}$" 564 if ptcFitType ==
'POLYNOMIAL':
566 stringTitle = f
"Polynomial (degree: {self.config.polynomialFitDegree})" 571 supTitleFontSize = 18
574 nAmps = len(fitPtcDict)
577 nRows = np.sqrt(nAmps)
578 mantissa, _ = np.modf(nRows)
580 nRows = int(nRows) + 1
586 f, ax = plt.subplots(nrows=nRows, ncols=nCols, sharex=
'col', sharey=
'row', figsize=(13, 10))
587 f2, ax2 = plt.subplots(nrows=nRows, ncols=nCols, sharex=
'col', sharey=
'row', figsize=(13, 10))
591 for i, (amp, a, a2)
in enumerate(zip(fitPtcDict, ax.flatten(), ax2.flatten())):
592 meanVecOriginal, varVecOriginal = fitPtcDict[amp][1], fitPtcDict[amp][2]
593 meanVecFinal, varVecFinal = fitPtcDict[amp][4], fitPtcDict[amp][5]
594 meanVecOutliers = np.setdiff1d(meanVecOriginal, meanVecFinal)
595 varVecOutliers = np.setdiff1d(varVecOriginal, varVecFinal)
596 pars, parsErr = fitPtcDict[amp][6], fitPtcDict[amp][7]
598 if ptcFitType ==
'ASTIERAPPROXIMATION':
599 ptcA00, ptcA00error = pars[0], parsErr[0]
600 ptcGain, ptcGainError = pars[1], parsErr[1]
601 ptcNoise = np.sqrt(np.fabs(pars[2]))
602 ptcNoiseError = 0.5*(parsErr[2]/np.fabs(pars[2]))*np.sqrt(np.fabs(pars[2]))
603 stringLegend = (f
"a00: {ptcA00:.2e}+/-{ptcA00error:.2e}" 604 f
"\n Gain: {ptcGain:.4}+/-{ptcGainError:.2e}" 605 f
"\n Noise: {ptcNoise:.4}+/-{ptcNoiseError:.2e}")
607 if ptcFitType ==
'POLYNOMIAL':
608 ptcGain, ptcGainError = 1./pars[1], np.fabs(1./pars[1])*(parsErr[1]/pars[1])
609 ptcNoise = np.sqrt(np.fabs(pars[0]))*ptcGain
610 ptcNoiseError = (0.5*(parsErr[0]/np.fabs(pars[0]))*(np.sqrt(np.fabs(pars[0]))))*ptcGain
611 stringLegend = (f
"Noise: {ptcNoise:.4}+/-{ptcNoiseError:.2e} \n" 612 f
"Gain: {ptcGain:.4}+/-{ptcGainError:.2e}")
614 minMeanVecFinal = np.min(meanVecFinal)
615 maxMeanVecFinal = np.max(meanVecFinal)
616 meanVecFit = np.linspace(minMeanVecFinal, maxMeanVecFinal, 100*len(meanVecFinal))
617 minMeanVecOriginal = np.min(meanVecOriginal)
618 maxMeanVecOriginal = np.max(meanVecOriginal)
619 deltaXlim = maxMeanVecOriginal - minMeanVecOriginal
621 a.plot(meanVecFit, ptcFunc(pars, meanVecFit), color=
'red')
622 a.plot(meanVecFinal, pars[0] + pars[1]*meanVecFinal, color=
'green', linestyle=
'--')
623 a.scatter(meanVecFinal, varVecFinal, c=
'blue', marker=
'o')
624 a.scatter(meanVecOutliers, varVecOutliers, c=
'magenta', marker=
's')
625 a.set_xlabel(
r'Mean signal ($\mu$, ADU)', fontsize=labelFontSize)
626 a.set_xticks(meanVecOriginal)
627 a.set_ylabel(
r'Variance (ADU$^2$)', fontsize=labelFontSize)
628 a.tick_params(labelsize=11)
629 a.text(0.03, 0.8, stringLegend, transform=a.transAxes, fontsize=legendFontSize)
630 a.set_xscale(
'linear', fontsize=labelFontSize)
631 a.set_yscale(
'linear', fontsize=labelFontSize)
632 a.set_title(amp, fontsize=titleFontSize)
633 a.set_xlim([minMeanVecOriginal - 0.2*deltaXlim, maxMeanVecOriginal + 0.2*deltaXlim])
636 a2.plot(meanVecFit, ptcFunc(pars, meanVecFit), color=
'red')
637 a2.scatter(meanVecFinal, varVecFinal, c=
'blue', marker=
'o')
638 a2.scatter(meanVecOutliers, varVecOutliers, c=
'magenta', marker=
's')
639 a2.set_xlabel(
r'Mean Signal ($\mu$, ADU)', fontsize=labelFontSize)
640 a2.set_ylabel(
r'Variance (ADU$^2$)', fontsize=labelFontSize)
641 a2.tick_params(labelsize=11)
642 a2.text(0.03, 0.8, stringLegend, transform=a2.transAxes, fontsize=legendFontSize)
645 a2.set_title(amp, fontsize=titleFontSize)
646 a2.set_xlim([minMeanVecOriginal, maxMeanVecOriginal])
648 f.suptitle(f
"PTC \n Fit: " + stringTitle, fontsize=20)
650 f2.suptitle(f
"PTC (log-log)", fontsize=20)
654 f, ax = plt.subplots(nrows=4, ncols=4, sharex=
'col', sharey=
'row', figsize=(13, 10))
655 for i, (amp, a)
in enumerate(zip(fitPtcDict, ax.flatten())):
656 timeVecFinal, meanVecFinal = nlDict[amp][0], nlDict[amp][1]
657 pars, _ = nlDict[amp][3], nlDict[amp][4]
658 c0, c0Error = pars[0], parsErr[0]
659 c1, c1Error = pars[1], parsErr[1]
660 c2, c2Error = pars[2], parsErr[2]
661 stringLegend = f
"c0: {c0:.4}+/-{c0Error:.2e}\n c1: {c1:.4}+/-{c1Error:.2e}" \
662 + f
"\n c2(NL): {c2:.2e}+/-{c2Error:.2e}" 663 a.scatter(timeVecFinal, meanVecFinal)
664 a.plot(timeVecFinal, self.
funcPolynomial(pars, timeVecFinal), color=
'red')
665 a.set_xlabel(
'Time (sec)', fontsize=labelFontSize)
666 a.set_xticks(timeVecFinal)
667 a.set_ylabel(
r'Mean signal ($\mu$, ADU)', fontsize=labelFontSize)
668 a.tick_params(labelsize=labelFontSize)
669 a.text(0.03, 0.75, stringLegend, transform=a.transAxes, fontsize=legendFontSize)
670 a.set_xscale(
'linear', fontsize=labelFontSize)
671 a.set_yscale(
'linear', fontsize=labelFontSize)
672 a.set_title(amp, fontsize=titleFontSize)
674 f.suptitle(
"Linearity \n Fit: " +
r"$\mu = c_0 + c_1 t + c_2 t^2$", fontsize=supTitleFontSize)
678 f, ax = plt.subplots(nrows=4, ncols=4, sharex=
'col', sharey=
'row', figsize=(13, 10))
679 for i, (amp, a)
in enumerate(zip(fitPtcDict, ax.flatten())):
680 meanVecFinal, linRes = nlDict[amp][1], nlDict[amp][2]
681 a.scatter(meanVecFinal, linRes)
682 a.axhline(y=0, color=
'k')
683 a.axvline(x=timeVecFinal[self.config.linResidualTimeIndex], color =
'g', linestyle =
'--')
684 a.set_xlabel(
r'Mean signal ($\mu$, ADU)', fontsize=labelFontSize)
685 a.set_xticks(meanVecFinal)
686 a.set_ylabel(
'LR (%)', fontsize=labelFontSize)
687 a.tick_params(labelsize=labelFontSize)
688 a.set_xscale(
'linear', fontsize=labelFontSize)
689 a.set_yscale(
'linear', fontsize=labelFontSize)
690 a.set_title(amp, fontsize=titleFontSize)
692 f.suptitle(
r"Linearity Residual: $100(1 - \mu_{\rm{ref}}/t_{\rm{ref}})/(\mu / t))$" +
"\n" +
693 r"$t_{\rm{ref}}$: " + f
"{timeVecFinal[2]} s", fontsize=supTitleFontSize)
def funcAstier(self, pars, x)
def _plotPtc(self, fitPtcDict, nlDict, ptcFitType, pdfPages)
def runDataRef(self, dataRef, visitPairs)
def fitPtcAndNl(self, fitVectorsDict, ptcFitType='POLYNOMIAL')
def funcPolynomial(self, pars, x)
def checkExpLengthEqual(exp1, exp2, v1=None, v2=None, raiseWithMessage=False)
def validateIsrConfig(isrTask, mandatory=None, forbidden=None, desirable=None, undesirable=None, checkTrim=True, logName=None)
def _fitBootstrap(self, initialParams, dataX, dataY, function, confidenceSigma=1.)
def __init__(self, args, kwargs)
def measureMeanVarPair(self, exposure1, exposure2, region=None)
def plot(self, dataRef, fitPtcDict, nlDict, ptcFitType='POLYNOMIAL')
def _fitLeastSq(self, initialParams, dataX, dataY, function)