23 __all__ = [
'MeasurePhotonTransferCurveTask',
24 'MeasurePhotonTransferCurveTaskConfig', ]
27 import matplotlib.pyplot
as plt
29 from matplotlib.backends.backend_pdf
import PdfPages
32 import lsst.pex.config
as pexConfig
35 from .utils
import (NonexistentDatasetTaskDataIdContainer, PairedVisitListTaskRunner,
36 checkExpLengthEqual, validateIsrConfig)
37 from scipy.optimize
import leastsq
38 import numpy.polynomial.polynomial
as poly
42 """Config class for photon transfer curve measurement task""" 43 isr = pexConfig.ConfigurableField(
45 doc=
"""Task to perform instrumental signature removal.""",
47 isrMandatorySteps = pexConfig.ListField(
49 doc=
"isr operations that must be performed for valid results. Raises if any of these are False.",
50 default=[
'doAssembleCcd']
52 isrForbiddenSteps = pexConfig.ListField(
54 doc=
"isr operations that must NOT be performed for valid results. Raises if any of these are True",
55 default=[
'doFlat',
'doFringe',
'doAddDistortionModel',
'doBrighterFatter',
'doUseOpticsTransmission',
56 'doUseFilterTransmission',
'doUseSensorTransmission',
'doUseAtmosphereTransmission']
58 isrDesirableSteps = pexConfig.ListField(
60 doc=
"isr operations that it is advisable to perform, but are not mission-critical." +
61 " WARNs are logged for any of these found to be False.",
62 default=[
'doBias',
'doDark',
'doCrosstalk',
'doDefect']
64 isrUndesirableSteps = pexConfig.ListField(
66 doc=
"isr operations that it is *not* advisable to perform in the general case, but are not" +
67 " forbidden as some use-cases might warrant them." +
68 " WARNs are logged for any of these found to be True.",
69 default=[
'doLinearize']
71 ccdKey = pexConfig.Field(
73 doc=
"The key by which to pull a detector from a dataId, e.g. 'ccd' or 'detector'.",
76 makePlots = pexConfig.Field(
78 doc=
"Plot the PTC curves?.",
81 ptcFitType = pexConfig.ChoiceField(
83 doc=
"Fit PTC to approximation in Astier+19 (Equation 16) or to a polynomial.",
86 "POLYNOMIAL":
"n-degree polynomial (use 'polynomialFitDegree' to set 'n').",
87 "ASTIERAPPROXIMATION":
"Approximation in Astier+19 (Eq. 16)." 90 polynomialFitDegree = pexConfig.Field(
92 doc=
"Degree of polynomial to fit the PTC, when 'ptcFitType'=POLYNOMIAL.",
95 binSize = pexConfig.Field(
97 doc=
"Bin the image by this factor in both dimensions.",
100 minMeanSignal = pexConfig.Field(
102 doc=
"Minimum value of mean signal (in ADU) to consider.",
105 maxMeanSignal = pexConfig.Field(
107 doc=
"Maximum value to of mean signal (in ADU) to consider.",
110 sigmaCutPtcOutliers = pexConfig.Field(
112 doc=
"Sigma cut for outlier rejection in PTC.",
115 maxIterationsPtcOutliers = pexConfig.Field(
117 doc=
"Maximum number of iterations for outlier rejection in PTC.",
120 doFitBootstrap = pexConfig.Field(
122 doc=
"Use bootstrap for the PTC fit parameters and errors?.",
125 linResidualTimeIndex = pexConfig.Field(
127 doc=
"Index position in time array for reference time in linearity residual calculation.",
133 """A class to calculate, fit, and plot a PTC from a set of flat pairs. 135 The Photon Transfer Curve (var(signal) vs mean(signal)) is a standard tool 136 used in astronomical detectors characterization (e.g., Janesick 2001, 137 Janesick 2007). This task calculates the PTC from a series of pairs of 138 flat-field images; each pair taken at identical exposure times. The 139 difference image of each pair is formed to eliminate fixed pattern noise, 140 and then the variance of the difference image and the mean of the average image 141 are used to produce the PTC. An n-degree polynomial or the approximation in Equation 142 16 of Astier+19 ("The Shape of the Photon Transfer Curve of CCD sensors", 143 arXiv:1905.08677) can be fitted to the PTC curve. These models include 144 parameters such as the gain (e/ADU) and readout noise. 150 Positional arguments passed to the Task constructor. None used at this 153 Keyword arguments passed on to the Task constructor. None used at this 158 RunnerClass = PairedVisitListTaskRunner
159 ConfigClass = MeasurePhotonTransferCurveTaskConfig
160 _DefaultName =
"measurePhotonTransferCurve" 163 pipeBase.CmdLineTask.__init__(self, *args, **kwargs)
164 self.makeSubtask(
"isr")
165 plt.interactive(
False)
167 self.config.isrForbiddenSteps, self.config.isrDesirableSteps, checkTrim=
False)
172 def _makeArgumentParser(cls):
173 """Augment argument parser for the MeasurePhotonTransferCurveTask.""" 175 parser.add_argument(
"--visit-pairs", dest=
"visitPairs", nargs=
"*",
176 help=
"Visit pairs to use. Each pair must be of the form INT,INT e.g. 123,456")
177 parser.add_id_argument(
"--id", datasetType=
"measurePhotonTransferCurveGainAndNoise",
178 ContainerClass=NonexistentDatasetTaskDataIdContainer,
179 help=
"The ccds to use, e.g. --id ccd=0..100")
184 """Run the Photon Transfer Curve (PTC) measurement task. 186 For a dataRef (which is each detector here), 187 and given a list of visit pairs at different exposure times, 192 dataRef : list of lsst.daf.persistence.ButlerDataRef 193 dataRef for the detector for the visits to be fit. 194 visitPairs : `iterable` of `tuple` of `int` 195 Pairs of visit numbers to be processed together 199 detNum = dataRef.dataId[self.config.ccdKey]
200 detector = dataRef.get(
'camera')[dataRef.dataId[self.config.ccdKey]]
201 amps = detector.getAmplifiers()
202 ampNames = [amp.getName()
for amp
in amps]
203 dataDict = {key: {}
for key
in ampNames}
204 fitVectorsDict = {key: ([], [], [])
for key
in ampNames}
206 self.log.
info(
'Measuring PTC using %s visits for detector %s' % (visitPairs, detNum))
208 for (v1, v2)
in visitPairs:
210 dataRef.dataId[
'visit'] = v1
212 dataRef.dataId[
'visit'] = v2
214 del dataRef.dataId[
'visit']
217 expTime = exp1.getInfo().getVisitInfo().getExposureTime()
221 data = dict(expTime=expTime, meanClip=mu, varClip=varDiff)
222 ampName = amp.getName()
223 dataDict[ampName][(v1, v2)] = data
224 fitVectorsDict[ampName][0].
append(expTime)
225 fitVectorsDict[ampName][1].
append(mu)
226 fitVectorsDict[ampName][2].
append(varDiff)
229 fitPtcDict, nlDict, gainDict, noiseDict = self.
fitPtcAndNl(fitVectorsDict,
230 ptcFitType=self.config.ptcFitType)
231 allDict = {
"data": dataDict,
"ptc": fitPtcDict,
"nl": nlDict}
232 gainNoiseNlDict = {
"gain": gainDict,
"noise": noiseDict,
"nl": nlDict}
234 if self.config.makePlots:
235 self.
plot(dataRef, fitPtcDict, nlDict, ptcFitType=self.config.ptcFitType)
238 self.log.
info(f
"Writing PTC and NL data to {dataRef.getUri(write=True)}")
239 dataRef.put(gainNoiseNlDict, datasetType=
"measurePhotonTransferCurveGainAndNoise")
240 dataRef.put(allDict, datasetType=
"measurePhotonTransferCurveDatasetAll")
242 self.log.
info(
'Finished measuring PTC for in detector %s' % detNum)
244 return pipeBase.Struct(exitStatus=0)
247 """Calculate the mean signal of two exposures and the variance of their difference. 251 exposure1 : `lsst.afw.image.exposure.exposure.ExposureF` 252 First exposure of flat field pair. 254 exposure2 : `lsst.afw.image.exposure.exposure.ExposureF` 255 Second exposure of flat field pair. 257 region : `lsst.geom.Box2I` 258 Region of each exposure where to perform the calculations (e.g, an amplifier). 264 0.5*(mu1 + mu2), where mu1, and mu2 are the clipped means of the regions in 268 Half of the clipped variance of the difference of the regions inthe two input 272 if region
is not None:
273 im1Area = exposure1.maskedImage[region]
274 im2Area = exposure2.maskedImage[region]
276 im1Area = exposure1.maskedImage
277 im2Area = exposure2.maskedImage
289 temp = im2Area.clone()
291 diffIm = im1Area.clone()
300 def _fitLeastSq(self, initialParams, dataX, dataY, function):
301 """Do a fit and estimate the parameter errors using using scipy.optimize.leastq. 303 optimize.leastsq returns the fractional covariance matrix. To estimate the 304 standard deviation of the fit parameters, multiply the entries of this matrix 305 by the reduced chi squared and take the square root of the diagon al elements. 309 initialParams : list of np.float 310 initial values for fit parameters. For ptcFitType=POLYNOMIAL, its length 311 determines the degree of the polynomial. 313 dataX : np.array of np.float 314 Data in the abscissa axis. 316 dataY : np.array of np.float 317 Data in the ordinate axis. 319 function : callable object (function) 320 Function to fit the data with. 324 pFitSingleLeastSquares : list of np.float 325 List with fitted parameters. 327 pErrSingleLeastSquares : list of np.float 328 List with errors for fitted parameters. 331 def errFunc(p, x, y):
334 pFit, pCov, infoDict, errMessage, success = leastsq(errFunc, initialParams,
335 args=(dataX, dataY), full_output=1, epsfcn=0.0001)
337 if (len(dataY) > len(initialParams))
and pCov
is not None:
338 reducedChiSq = (errFunc(pFit, dataX, dataY)**2).sum()/(len(dataY)-len(initialParams))
344 for i
in range(len(pFit)):
345 errorVec.append(np.fabs(pCov[i][i])**0.5)
347 pFitSingleLeastSquares = pFit
348 pErrSingleLeastSquares = np.array(errorVec)
350 return pFitSingleLeastSquares, pErrSingleLeastSquares
352 def _fitBootstrap(self, initialParams, dataX, dataY, function, confidenceSigma=1.):
353 """Do a fit using least squares and bootstrap to estimate parameter errors. 355 The bootstrap error bars are calculated by fitting 100 random data sets. 359 initialParams : list of np.float 360 initial values for fit parameters. For ptcFitType=POLYNOMIAL, its length 361 determines the degree of the polynomial. 363 dataX : np.array of np.float 364 Data in the abscissa axis. 366 dataY : np.array of np.float 367 Data in the ordinate axis. 369 function : callable object (function) 370 Function to fit the data with. 372 confidenceSigma : np.float 373 Number of sigmas that determine confidence interval for the bootstrap errors. 377 pFitBootstrap : list of np.float 378 List with fitted parameters. 380 pErrBootstrap : list of np.float 381 List with errors for fitted parameters. 384 def errFunc(p, x, y):
388 pFit, _ = leastsq(errFunc, initialParams, args=(dataX, dataY), full_output=0)
391 residuals = errFunc(pFit, dataX, dataY)
392 sigmaErrTotal = np.std(residuals)
397 randomDelta = np.random.normal(0., sigmaErrTotal, len(dataY))
398 randomDataY = dataY + randomDelta
399 randomFit, _ = leastsq(errFunc, initialParams,
400 args=(dataX, randomDataY), full_output=0)
401 pars.append(randomFit)
402 pars = np.array(pars)
403 meanPfit = np.mean(pars, 0)
406 nSigma = confidenceSigma
407 errPfit = nSigma*np.std(pars, 0)
408 pFitBootstrap = meanPfit
409 pErrBootstrap = errPfit
410 return pFitBootstrap, pErrBootstrap
413 """Polynomial function definition""" 414 return poly.polyval(x, [*pars])
417 """Single brighter-fatter parameter model for PTC; Equation 16 of Astier+19""" 418 a00, gain, noise = pars
419 return 0.5/(a00*gain*gain)*(np.exp(2*a00*x*gain)-1) + noise/(gain*gain)
422 """Function to fit PTC, and calculate linearity and linearity residual 426 fitVectorsDicti : `dict` 427 Dictionary with exposure time, mean, and variance vectors in a tuple 429 Fit a 'POLYNOMIAL' (degree: 'polynomialFitDegree') or ' 430 ASTIERAPPROXIMATION' to the PTC 435 Dictionary of the form fitPtcDict[amp] = 436 (meanVec, varVec, parsFit, parsFitErr, index) 438 Dictionary of the form nlDict[amp] = 439 (timeVec, meanVec, linResidual, parsFit, parsFitErr) 441 if ptcFitType ==
'ASTIERAPPROXIMATION':
443 parsIniPtc = [-1e-9, 1.0, 10.]
444 if ptcFitType ==
'POLYNOMIAL':
446 parsIniPtc = np.repeat(1., self.config.polynomialFitDegree + 1)
448 parsIniNl = [1., 1., 1.]
449 fitPtcDict = {key: {}
for key
in fitVectorsDict}
450 nlDict = {key: {}
for key
in fitVectorsDict}
451 gainDict = {key: {}
for key
in fitVectorsDict}
452 noiseDict = {key: {}
for key
in fitVectorsDict}
454 def errFunc(p, x, y):
455 return ptcFunc(p, x) - y
457 maxIterationsPtcOutliers = self.config.maxIterationsPtcOutliers
458 for amp
in fitVectorsDict:
459 timeVec, meanVec, varVec = fitVectorsDict[amp]
460 timeVecOriginal = np.array(timeVec)
461 meanVecOriginal = np.array(meanVec)
462 varVecOriginal = np.array(varVec)
463 index0 = ((meanVecOriginal > self.config.minMeanSignal) &
464 (meanVecOriginal <= self.config.maxMeanSignal))
467 sigmaCutPtcOutliers = self.config.sigmaCutPtcOutliers
468 maxIterationsPtcOutliers = self.config.maxIterationsPtcOutliers
469 timeTempVec = timeVecOriginal[index0]
470 meanTempVec = meanVecOriginal[index0]
471 varTempVec = varVecOriginal[index0]
472 while count <= maxIterationsPtcOutliers:
473 pars, cov = leastsq(errFunc, parsIniPtc, args=(meanTempVec,
474 varTempVec), full_output=0)
475 sigResids = (varTempVec -
476 ptcFunc(pars, meanTempVec))/np.sqrt(varTempVec)
477 index =
list(np.where(np.abs(sigResids) < sigmaCutPtcOutliers)[0])
478 timeTempVec = timeTempVec[index]
479 meanTempVec = meanTempVec[index]
480 varTempVec = varTempVec[index]
484 timeVecFinal, meanVecFinal, varVecFinal = timeTempVec, meanTempVec, varTempVec
485 if (len(meanVecFinal) - len(meanVecOriginal)) > 0:
486 self.log.
info((f
"Number of points discarded in PTC of amplifier {amp}:" +
487 "{len(meanVecFinal)-len(meanVecOriginal)} out of {len(meanVecOriginal)}"))
489 if (len(meanVecFinal) < len(parsIniPtc)):
490 raise RuntimeError(f
"Not enough data points ({len(meanVecFinal)}) compared to the number of" +
491 "parameters of the PTC model({len(parsIniPtc)}).")
493 if self.config.doFitBootstrap:
494 parsFit, parsFitErr = self.
_fitBootstrap(parsIniPtc, meanVecFinal, varVecFinal, ptcFunc)
496 parsFit, parsFitErr = self.
_fitLeastSq(parsIniPtc, meanVecFinal, varVecFinal, ptcFunc)
498 fitPtcDict[amp] = (timeVecOriginal, meanVecOriginal, varVecOriginal, timeVecFinal,
499 meanVecFinal, varVecFinal, parsFit, parsFitErr)
501 if ptcFitType ==
'ASTIERAPPROXIMATION':
503 ptcGainErr = parsFitErr[1]
504 ptcNoise = np.sqrt(np.fabs(parsFit[2]))
505 ptcNoiseErr = 0.5*(parsFitErr[2]/np.fabs(parsFit[2]))*np.sqrt(np.fabs(parsFit[2]))
506 if ptcFitType ==
'POLYNOMIAL':
507 ptcGain = 1./parsFit[1]
508 ptcGainErr = np.fabs(1./parsFit[1])*(parsFitErr[1]/parsFit[1])
509 ptcNoise = np.sqrt(np.fabs(parsFit[0]))*ptcGain
510 ptcNoiseErr = (0.5*(parsFitErr[0]/np.fabs(parsFit[0]))*(np.sqrt(np.fabs(parsFit[0]))))*ptcGain
512 gainDict[amp] = (ptcGain, ptcGainErr)
513 noiseDict[amp] = (ptcNoise, ptcNoiseErr)
517 if self.config.doFitBootstrap:
518 parsFit, parsFitErr = self.
_fitBootstrap(parsIniNl, timeVecFinal, meanVecFinal,
521 parsFit, parsFitErr = self.
_fitLeastSq(parsIniNl, timeVecFinal, meanVecFinal,
523 linResidualTimeIndex = self.config.linResidualTimeIndex
524 if timeVecFinal[linResidualTimeIndex] == 0.0:
525 raise RuntimeError(
"Reference time for linearity residual can't be 0.0")
526 linResidual = 100*(1 - ((meanVecFinal[linResidualTimeIndex] /
527 timeVecFinal[linResidualTimeIndex]) / (meanVecFinal/timeVecFinal)))
528 nlDict[amp] = (timeVecFinal, meanVecFinal, linResidual, parsFit, parsFitErr)
530 return fitPtcDict, nlDict, gainDict, noiseDict
532 def plot(self, dataRef, fitPtcDict, nlDict, ptcFitType='POLYNOMIAL'):
533 dirname = dataRef.getUri(datasetType=
'cpPipePlotRoot', write=
True)
534 if not os.path.exists(dirname):
537 detNum = dataRef.dataId[self.config.ccdKey]
538 filename = f
"PTC_det{detNum}.pdf" 539 filenameFull = os.path.join(dirname, filename)
540 with PdfPages(filenameFull)
as pdfPages:
541 self.
_plotPtc(fitPtcDict, nlDict, ptcFitType, pdfPages)
543 def _plotPtc(self, fitPtcDict, nlDict, ptcFitType, pdfPages):
544 """Plot PTC, linearity, and linearity residual per amplifier""" 546 if ptcFitType ==
'ASTIERAPPROXIMATION':
548 stringTitle =
r"Var = $\frac{1}{2g^2a_{00}}(\exp (2a_{00} \mu g) - 1) + \frac{n_{00}}{g^2}$" 550 if ptcFitType ==
'POLYNOMIAL':
552 stringTitle = f
"Polynomial (degree: {self.config.polynomialFitDegree})" 557 supTitleFontSize = 18
560 nAmps = len(fitPtcDict)
563 nRows = np.sqrt(nAmps)
564 mantissa, _ = np.modf(nRows)
566 nRows = int(nRows) + 1
572 f, ax = plt.subplots(nrows=nRows, ncols=nCols, sharex=
'col', sharey=
'row', figsize=(13, 10))
573 f2, ax2 = plt.subplots(nrows=nRows, ncols=nCols, sharex=
'col', sharey=
'row', figsize=(13, 10))
577 for i, (amp, a, a2)
in enumerate(zip(fitPtcDict, ax.flatten(), ax2.flatten())):
578 meanVecOriginal, varVecOriginal = fitPtcDict[amp][1], fitPtcDict[amp][2]
579 meanVecFinal, varVecFinal = fitPtcDict[amp][4], fitPtcDict[amp][5]
580 meanVecOutliers = np.setdiff1d(meanVecOriginal, meanVecFinal)
581 varVecOutliers = np.setdiff1d(varVecOriginal, varVecFinal)
582 pars, parsErr = fitPtcDict[amp][6], fitPtcDict[amp][7]
584 if ptcFitType ==
'ASTIERAPPROXIMATION':
585 ptcA00, ptcA00error = pars[0], parsErr[0]
586 ptcGain, ptcGainError = pars[1], parsErr[1]
587 ptcNoise = np.sqrt(np.fabs(pars[2]))
588 ptcNoiseError = 0.5*(parsErr[2]/np.fabs(pars[2]))*np.sqrt(np.fabs(pars[2]))
589 stringLegend = (f
"a00: {ptcA00:.2e}+/-{ptcA00error:.2e}" 590 f
"\n Gain: {ptcGain:.4}+/-{ptcGainError:.2e}" 591 f
"\n Noise: {ptcNoise:.4}+/-{ptcNoiseError:.2e}")
593 if ptcFitType ==
'POLYNOMIAL':
594 ptcGain, ptcGainError = 1./pars[1], np.fabs(1./pars[1])*(parsErr[1]/pars[1])
595 ptcNoise = np.sqrt(np.fabs(pars[0]))*ptcGain
596 ptcNoiseError = (0.5*(parsErr[0]/np.fabs(pars[0]))*(np.sqrt(np.fabs(pars[0]))))*ptcGain
597 stringLegend = (f
"Noise: {ptcNoise:.4}+/-{ptcNoiseError:.2e} \n" 598 f
"Gain: {ptcGain:.4}+/-{ptcGainError:.2e}")
600 minMeanVecFinal = np.min(meanVecFinal)
601 maxMeanVecFinal = np.max(meanVecFinal)
602 meanVecFit = np.linspace(minMeanVecFinal, maxMeanVecFinal, 100*len(meanVecFinal))
603 minMeanVecOriginal = np.min(meanVecOriginal)
604 maxMeanVecOriginal = np.max(meanVecOriginal)
605 deltaXlim = maxMeanVecOriginal - minMeanVecOriginal
607 a.plot(meanVecFit, ptcFunc(pars, meanVecFit), color=
'red')
608 a.plot(meanVecFinal, pars[0] + pars[1]*meanVecFinal, color=
'green', linestyle=
'--')
609 a.scatter(meanVecFinal, varVecFinal, c=
'blue', marker=
'o')
610 a.scatter(meanVecOutliers, varVecOutliers, c=
'magenta', marker=
's')
611 a.set_xlabel(
r'Mean signal ($\mu$, ADU)', fontsize=labelFontSize)
612 a.set_xticks(meanVecOriginal)
613 a.set_ylabel(
r'Variance (ADU$^2$)', fontsize=labelFontSize)
614 a.tick_params(labelsize=11)
615 a.text(0.03, 0.8, stringLegend, transform=a.transAxes, fontsize=legendFontSize)
616 a.set_xscale(
'linear', fontsize=labelFontSize)
617 a.set_yscale(
'linear', fontsize=labelFontSize)
618 a.set_title(amp, fontsize=titleFontSize)
619 a.set_xlim([minMeanVecOriginal - 0.2*deltaXlim, maxMeanVecOriginal + 0.2*deltaXlim])
622 a2.plot(meanVecFit, ptcFunc(pars, meanVecFit), color=
'red')
623 a2.scatter(meanVecFinal, varVecFinal, c=
'blue', marker=
'o')
624 a2.scatter(meanVecOutliers, varVecOutliers, c=
'magenta', marker=
's')
625 a2.set_xlabel(
r'Mean Signal ($\mu$, ADU)', fontsize=labelFontSize)
626 a2.set_ylabel(
r'Variance (ADU$^2$)', fontsize=labelFontSize)
627 a2.tick_params(labelsize=11)
628 a2.text(0.03, 0.8, stringLegend, transform=a2.transAxes, fontsize=legendFontSize)
631 a2.set_title(amp, fontsize=titleFontSize)
632 a2.set_xlim([minMeanVecOriginal, maxMeanVecOriginal])
634 f.suptitle(f
"PTC \n Fit: " + stringTitle, fontsize=20)
636 f2.suptitle(f
"PTC (log-log)", fontsize=20)
640 f, ax = plt.subplots(nrows=4, ncols=4, sharex=
'col', sharey=
'row', figsize=(13, 10))
641 for i, (amp, a)
in enumerate(zip(fitPtcDict, ax.flatten())):
642 timeVecFinal, meanVecFinal = nlDict[amp][0], nlDict[amp][1]
643 pars, _ = nlDict[amp][3], nlDict[amp][4]
644 c0, c0Error = pars[0], parsErr[0]
645 c1, c1Error = pars[1], parsErr[1]
646 c2, c2Error = pars[2], parsErr[2]
647 stringLegend = f
"c0: {c0:.4}+/-{c0Error:.2e}\n c1: {c1:.4}+/-{c1Error:.2e}" \
648 + f
"\n c2(NL): {c2:.2e}+/-{c2Error:.2e}" 649 a.scatter(timeVecFinal, meanVecFinal)
650 a.plot(timeVecFinal, self.
funcPolynomial(pars, timeVecFinal), color=
'red')
651 a.set_xlabel(
'Time (sec)', fontsize=labelFontSize)
652 a.set_xticks(timeVecFinal)
653 a.set_ylabel(
r'Mean signal ($\mu$, ADU)', fontsize=labelFontSize)
654 a.tick_params(labelsize=labelFontSize)
655 a.text(0.03, 0.75, stringLegend, transform=a.transAxes, fontsize=legendFontSize)
656 a.set_xscale(
'linear', fontsize=labelFontSize)
657 a.set_yscale(
'linear', fontsize=labelFontSize)
658 a.set_title(amp, fontsize=titleFontSize)
660 f.suptitle(
"Linearity \n Fit: " +
r"$\mu = c_0 + c_1 t + c_2 t^2$", fontsize=supTitleFontSize)
664 f, ax = plt.subplots(nrows=4, ncols=4, sharex=
'col', sharey=
'row', figsize=(13, 10))
665 for i, (amp, a)
in enumerate(zip(fitPtcDict, ax.flatten())):
666 meanVecFinal, linRes = nlDict[amp][1], nlDict[amp][2]
667 a.scatter(meanVecFinal, linRes)
668 a.axhline(y=0, color=
'k')
669 a.axvline(x=timeVecFinal[self.config.linResidualTimeIndex], color =
'g', linestyle =
'--')
670 a.set_xlabel(
r'Mean signal ($\mu$, ADU)', fontsize=labelFontSize)
671 a.set_xticks(meanVecFinal)
672 a.set_ylabel(
'LR (%)', fontsize=labelFontSize)
673 a.tick_params(labelsize=labelFontSize)
674 a.set_xscale(
'linear', fontsize=labelFontSize)
675 a.set_yscale(
'linear', fontsize=labelFontSize)
676 a.set_title(amp, fontsize=titleFontSize)
678 f.suptitle(
r"Linearity Residual: $100(1 - \mu_{\rm{ref}}/t_{\rm{ref}})/(\mu / t))$" +
"\n" +
679 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)
std::shared_ptr< FrameSet > append(FrameSet const &first, FrameSet const &second)
Construct a FrameSet that performs two transformations in series.
def checkExpLengthEqual(exp1, exp2, v1=None, v2=None, raiseWithMessage=False)
Statistics makeStatistics(lsst::afw::math::MaskedVector< EntryT > const &mv, std::vector< WeightPixel > const &vweights, int const flags, StatisticsControl const &sctrl=StatisticsControl())
The makeStatistics() overload to handle lsst::afw::math::MaskedVector<>
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')
std::shared_ptr< ImageT > binImage(ImageT const &inImage, int const binsize, lsst::afw::math::Property const flags=lsst::afw::math::MEAN)
def _fitLeastSq(self, initialParams, dataX, dataY, function)
daf::base::PropertyList * list