23 __all__ = [
'PlotPhotonTransferCurveTask']
26 import matplotlib.pyplot
as plt
27 import matplotlib
as mpl
28 from matplotlib
import gridspec
30 from matplotlib.backends.backend_pdf
import PdfPages
37 from .utils
import (funcAstier, funcPolynomial, NonexistentDatasetTaskDataIdContainer,
38 calculateWeightedReducedChi2)
39 from matplotlib.ticker
import MaxNLocator
41 from .astierCovPtcFit
import computeApproximateAcoeffs
45 """Config class for photon transfer curve measurement task"""
46 datasetFileName = pexConfig.Field(
48 doc=
"datasetPtc file name (pkl)",
51 linearizerFileName = pexConfig.Field(
53 doc=
"linearizer file name (fits)",
56 ccdKey = pexConfig.Field(
58 doc=
"The key by which to pull a detector from a dataId, e.g. 'ccd' or 'detector'.",
61 signalElectronsRelativeA = pexConfig.Field(
63 doc=
"Signal value for relative systematic bias between different methods of estimating a_ij "
64 "(Fig. 15 of Astier+19).",
67 plotNormalizedCovariancesNumberOfBins = pexConfig.Field(
69 doc=
"Number of bins in `plotNormalizedCovariancesNumber` function "
70 "(Fig. 8, 10., of Astier+19).",
76 """A class to plot the dataset from MeasurePhotonTransferCurveTask.
82 Positional arguments passed to the Task constructor. None used at this
85 Keyword arguments passed on to the Task constructor. None used at this
90 ConfigClass = PlotPhotonTransferCurveTaskConfig
91 _DefaultName =
"plotPhotonTransferCurve"
94 pipeBase.CmdLineTask.__init__(self, *args, **kwargs)
95 plt.interactive(
False)
96 self.config.validate()
100 def _makeArgumentParser(cls):
101 """Augment argument parser for the MeasurePhotonTransferCurveTask."""
103 parser.add_id_argument(
"--id", datasetType=
"photonTransferCurveDataset",
104 ContainerClass=NonexistentDatasetTaskDataIdContainer,
105 help=
"The ccds to use, e.g. --id ccd=0..100")
110 """Run the Photon Transfer Curve (PTC) plotting measurement task.
114 dataRef : list of lsst.daf.persistence.ButlerDataRef
115 dataRef for the detector for the visits to be fit.
118 datasetFile = self.config.datasetFileName
120 with open(datasetFile,
"rb")
as f:
121 datasetPtc = pickle.load(f)
123 dirname = dataRef.getUri(datasetType=
'cpPipePlotRoot', write=
True)
124 if not os.path.exists(dirname):
127 detNum = dataRef.dataId[self.config.ccdKey]
128 filename = f
"PTC_det{detNum}.pdf"
129 filenameFull = os.path.join(dirname, filename)
131 if self.config.linearizerFileName:
132 linearizer = isr.linearize.Linearizer.readFits(self.config.linearizerFileName)
135 self.
run(filenameFull, datasetPtc, linearizer=linearizer, log=self.log)
137 return pipeBase.Struct(exitStatus=0)
139 def run(self, filenameFull, datasetPtc, linearizer=None, log=None):
140 """Make the plots for the PTC task"""
141 ptcFitType = datasetPtc.ptcFitType
142 with PdfPages(filenameFull)
as pdfPages:
143 if ptcFitType
in [
"FULLCOVARIANCE", ]:
146 elif ptcFitType
in [
"EXPAPPROXIMATION",
"POLYNOMIAL"]:
149 raise RuntimeError(f
"The input dataset had an invalid dataset.ptcFitType: {ptcFitType}. \n" +
150 "Options: 'FULLCOVARIANCE', EXPAPPROXIMATION, or 'POLYNOMIAL'.")
158 """Make plots for MeasurePhotonTransferCurve task when doCovariancesAstier=True.
160 This function call other functions that mostly reproduce the plots in Astier+19.
161 Most of the code is ported from Pierre Astier's repository https://github.com/PierreAstier/bfptc
166 Dictionary of CovFit objects, with amp names as keys.
169 Dictionary of CovFit objects, with amp names as keys (b=0 in Eq. 20 of Astier+19).
171 pdfPages: `matplotlib.backends.backend_pdf.PdfPages`
172 PDF file where the plots will be saved.
174 log : `lsst.log.Log`, optional
175 Logger to handle messages
179 numberOfBins=self.config.plotNormalizedCovariancesNumberOfBins,
182 numberOfBins=self.config.plotNormalizedCovariancesNumberOfBins,
185 numberOfBins=self.config.plotNormalizedCovariancesNumberOfBins,
197 """Plot covariances and models: Cov00, Cov10, Cov01.
199 Figs. 6 and 7 of Astier+19
204 Dictionary of CovFit objects, with amp names as keys.
206 pdfPages: `matplotlib.backends.backend_pdf.PdfPages`
207 PDF file where the plots will be saved.
213 supTitleFontSize = 18
219 nRows = np.sqrt(nAmps)
220 mantissa, _ = np.modf(nRows)
222 nRows = int(nRows) + 1
228 f, ax = plt.subplots(nrows=nRows, ncols=nCols, sharex=
'col', sharey=
'row', figsize=(13, 10))
229 f2, ax2 = plt.subplots(nrows=nRows, ncols=nCols, sharex=
'col', sharey=
'row', figsize=(13, 10))
230 fResCov00, axResCov00 = plt.subplots(nrows=nRows, ncols=nCols, sharex=
'col', sharey=
'row',
232 fCov01, axCov01 = plt.subplots(nrows=nRows, ncols=nCols, sharex=
'col', sharey=
'row', figsize=(13, 10))
233 fCov10, axCov10 = plt.subplots(nrows=nRows, ncols=nCols, sharex=
'col', sharey=
'row', figsize=(13, 10))
235 for i, (fitPair, a, a2, aResVar, a3, a4)
in enumerate(zip(covFits.items(), ax.flatten(),
236 ax2.flatten(), axResCov00.flatten(),
237 axCov01.flatten(), axCov10.flatten())):
242 (meanVecOriginal, varVecOriginal, varVecModelOriginal,
243 weightsOriginal, varMask) = fit.getFitData(0, 0)
244 meanVecFinal, varVecFinal = meanVecOriginal[varMask], varVecOriginal[varMask]
245 varVecModelFinal = varVecModelOriginal[varMask]
246 meanVecOutliers = meanVecOriginal[np.invert(varMask)]
247 varVecOutliers = varVecOriginal[np.invert(varMask)]
248 varWeightsFinal = weightsOriginal[varMask]
251 varWeightsFinal, len(meanVecFinal), 4)
253 (meanVecOrigCov01, varVecOrigCov01, varVecModelOrigCov01,
254 _, maskCov01) = fit.getFitData(0, 1)
255 meanVecFinalCov01, varVecFinalCov01 = meanVecOrigCov01[maskCov01], varVecOrigCov01[maskCov01]
256 varVecModelFinalCov01 = varVecModelOrigCov01[maskCov01]
257 meanVecOutliersCov01 = meanVecOrigCov01[np.invert(maskCov01)]
258 varVecOutliersCov01 = varVecOrigCov01[np.invert(maskCov01)]
260 (meanVecOrigCov10, varVecOrigCov10, varVecModelOrigCov10,
261 _, maskCov10) = fit.getFitData(1, 0)
262 meanVecFinalCov10, varVecFinalCov10 = meanVecOrigCov10[maskCov10], varVecOrigCov10[maskCov10]
263 varVecModelFinalCov10 = varVecModelOrigCov10[maskCov10]
264 meanVecOutliersCov10 = meanVecOrigCov10[np.invert(maskCov10)]
265 varVecOutliersCov10 = varVecOrigCov10[np.invert(maskCov10)]
268 par2 = np.polyfit(meanVecFinal, varVecFinal, 2, w=varWeightsFinal)
269 varModelFinalQuadratic = np.polyval(par2, meanVecFinal)
271 varWeightsFinal, len(meanVecFinal), 3)
275 fitNoB.params[
'c'].fix(val=0)
276 fitNoB.fitFullModel()
277 (meanVecFinalNoB, varVecFinalNoB, varVecModelFinalNoB,
278 varWeightsFinalNoB, maskNoB) = fitNoB.getFitData(0, 0, returnMasked=
True)
280 varWeightsFinalNoB, len(meanVecFinalNoB), 3)
282 if len(meanVecFinal):
283 stringLegend = (f
"Gain: {fit.getGain():.4} e/DN \n" +
284 f
"Noise: {fit.getRon():.4} e \n" +
285 r"$a_{00}$: %.3e 1/e"%fit.getA()[0, 0] +
286 "\n" +
r"$b_{00}$: %.3e 1/e"%fit.getB()[0, 0])
287 minMeanVecFinal = np.min(meanVecFinal)
288 maxMeanVecFinal = np.max(meanVecFinal)
289 deltaXlim = maxMeanVecFinal - minMeanVecFinal
291 a.set_xlabel(
r'Mean signal ($\mu$, DN)', fontsize=labelFontSize)
292 a.set_ylabel(
r'Variance (DN$^2$)', fontsize=labelFontSize)
293 a.tick_params(labelsize=11)
294 a.set_xscale(
'linear', fontsize=labelFontSize)
295 a.set_yscale(
'linear', fontsize=labelFontSize)
296 a.scatter(meanVecFinal, varVecFinal, c=
'blue', marker=
'o', s=markerSize)
297 a.scatter(meanVecOutliers, varVecOutliers, c=
'magenta', marker=
's', s=markerSize)
298 a.plot(meanVecFinal, varVecModelFinal, color=
'red', lineStyle=
'-')
299 a.text(0.03, 0.7, stringLegend, transform=a.transAxes, fontsize=legendFontSize)
300 a.set_title(amp, fontsize=titleFontSize)
301 a.set_xlim([minMeanVecFinal - 0.2*deltaXlim, maxMeanVecFinal + 0.2*deltaXlim])
304 a2.set_xlabel(
r'Mean Signal ($\mu$, DN)', fontsize=labelFontSize)
305 a2.set_ylabel(
r'Variance (DN$^2$)', fontsize=labelFontSize)
306 a2.tick_params(labelsize=11)
309 a2.plot(meanVecFinal, varVecModelFinal, color=
'red', lineStyle=
'-')
310 a2.scatter(meanVecFinal, varVecFinal, c=
'blue', marker=
'o', s=markerSize)
311 a2.scatter(meanVecOutliers, varVecOutliers, c=
'magenta', marker=
's', s=markerSize)
312 a2.text(0.03, 0.7, stringLegend, transform=a2.transAxes, fontsize=legendFontSize)
313 a2.set_title(amp, fontsize=titleFontSize)
314 a2.set_xlim([minMeanVecFinal, maxMeanVecFinal])
317 aResVar.set_xlabel(
r'Mean signal ($\mu$, DN)', fontsize=labelFontSize)
318 aResVar.set_ylabel(
r'Residuals (DN$^2$)', fontsize=labelFontSize)
319 aResVar.tick_params(labelsize=11)
320 aResVar.set_xscale(
'linear', fontsize=labelFontSize)
321 aResVar.set_yscale(
'linear', fontsize=labelFontSize)
322 aResVar.plot(meanVecFinal, varVecFinal - varVecModelFinal, color=
'blue', lineStyle=
'-',
323 label=
r'Full fit ($\chi_{\rm{red}}^2$: %g)'%chi2FullModelVar)
324 aResVar.plot(meanVecFinal, varVecFinal - varModelFinalQuadratic, color=
'red', lineStyle=
'-',
325 label=
r'Quadratic fit ($\chi_{\rm{red}}^2$: %g)'%chi2QuadModelVar)
326 aResVar.plot(meanVecFinalNoB, varVecFinalNoB - varVecModelFinalNoB, color=
'green',
328 label=
r'Full fit (b=0) ($\chi_{\rm{red}}^2$: %g)'%chi2FullModelNoBVar)
329 aResVar.axhline(color=
'black')
330 aResVar.set_title(amp, fontsize=titleFontSize)
331 aResVar.set_xlim([minMeanVecFinal - 0.2*deltaXlim, maxMeanVecFinal + 0.2*deltaXlim])
332 aResVar.legend(fontsize=7)
334 a3.set_xlabel(
r'Mean signal ($\mu$, DN)', fontsize=labelFontSize)
335 a3.set_ylabel(
r'Cov01 (DN$^2$)', fontsize=labelFontSize)
336 a3.tick_params(labelsize=11)
337 a3.set_xscale(
'linear', fontsize=labelFontSize)
338 a3.set_yscale(
'linear', fontsize=labelFontSize)
339 a3.scatter(meanVecFinalCov01, varVecFinalCov01, c=
'blue', marker=
'o', s=markerSize)
340 a3.scatter(meanVecOutliersCov01, varVecOutliersCov01, c=
'magenta', marker=
's', s=markerSize)
341 a3.plot(meanVecFinalCov01, varVecModelFinalCov01, color=
'red', lineStyle=
'-')
342 a3.set_title(amp, fontsize=titleFontSize)
343 a3.set_xlim([minMeanVecFinal - 0.2*deltaXlim, maxMeanVecFinal + 0.2*deltaXlim])
345 a4.set_xlabel(
r'Mean signal ($\mu$, DN)', fontsize=labelFontSize)
346 a4.set_ylabel(
r'Cov10 (DN$^2$)', fontsize=labelFontSize)
347 a4.tick_params(labelsize=11)
348 a4.set_xscale(
'linear', fontsize=labelFontSize)
349 a4.set_yscale(
'linear', fontsize=labelFontSize)
350 a4.scatter(meanVecFinalCov10, varVecFinalCov10, c=
'blue', marker=
'o', s=markerSize)
351 a4.scatter(meanVecOutliersCov10, varVecOutliersCov10, c=
'magenta', marker=
's', s=markerSize)
352 a4.plot(meanVecFinalCov10, varVecModelFinalCov10, color=
'red', lineStyle=
'-')
353 a4.set_title(amp, fontsize=titleFontSize)
354 a4.set_xlim([minMeanVecFinal - 0.2*deltaXlim, maxMeanVecFinal + 0.2*deltaXlim])
357 a.set_title(f
"{amp} (BAD)", fontsize=titleFontSize)
358 a2.set_title(f
"{amp} (BAD)", fontsize=titleFontSize)
359 a3.set_title(f
"{amp} (BAD)", fontsize=titleFontSize)
360 a4.set_title(f
"{amp} (BAD)", fontsize=titleFontSize)
362 f.suptitle(
"PTC from covariances as in Astier+19 \n Fit: Eq. 20, Astier+19",
363 fontsize=supTitleFontSize)
365 f2.suptitle(
"PTC from covariances as in Astier+19 (log-log) \n Fit: Eq. 20, Astier+19",
366 fontsize=supTitleFontSize)
368 fResCov00.suptitle(
"Residuals (data-model) for Cov00 (Var)", fontsize=supTitleFontSize)
369 pdfPages.savefig(fResCov00)
370 fCov01.suptitle(
"Cov01 as in Astier+19 (nearest parallel neighbor covariance) \n" +
371 " Fit: Eq. 20, Astier+19", fontsize=supTitleFontSize)
372 pdfPages.savefig(fCov01)
373 fCov10.suptitle(
"Cov10 as in Astier+19 (nearest serial neighbor covariance) \n" +
374 "Fit: Eq. 20, Astier+19", fontsize=supTitleFontSize)
375 pdfPages.savefig(fCov10)
380 numberOfBins=10, plotData=True, topPlot=False, log=None):
381 """Plot C_ij/mu vs mu.
383 Figs. 8, 10, and 11 of Astier+19
388 Dictionary of CovFit objects, with amp names as keys.
391 Dictionary of CovFit objects, with amp names as keys (b=0 in Eq. 20 of Astier+19).
399 pdfPages: `matplotlib.backends.backend_pdf.PdfPages`
400 PDF file where the plots will be saved.
402 offset : `float`, optional
403 Constant offset factor to plot covariances in same panel (so they don't overlap).
405 numberOfBins : `int`, optional
406 Number of bins for top and bottom plot.
408 plotData : `bool`, optional
409 Plot the data points?
411 topPlot : `bool`, optional
412 Plot the top plot with the covariances, and the bottom plot with the model residuals?
414 log : `lsst.log.Log`, optional
415 Logger to handle messages.
418 lchi2, la, lb, lcov = [], [], [], []
421 fig = plt.figure(figsize=(8, 10))
422 gs = gridspec.GridSpec(2, 1, height_ratios=[3, 1])
424 ax0 = plt.subplot(gs[0])
425 plt.setp(ax0.get_xticklabels(), visible=
False)
427 fig = plt.figure(figsize=(8, 8))
428 ax0 = plt.subplot(111)
429 ax0.ticklabel_format(style=
'sci', axis=
'x', scilimits=(0, 0))
430 ax0.tick_params(axis=
'both', labelsize=
'x-large')
431 mue, rese, wce = [], [], []
432 mueNoB, reseNoB, wceNoB = [], [], []
433 for counter, (amp, fit)
in enumerate(covFits.items()):
434 mu, cov, model, weightCov, _ = fit.getFitData(i, j, divideByMu=
True, returnMasked=
True)
435 wres = (cov-model)*weightCov
436 chi2 = ((wres*wres).sum())/(len(mu)-3)
439 rese += list(cov - model)
440 wce += list(weightCov)
442 fitNoB = covFitsNoB[amp]
443 (muNoB, covNoB, modelNoB,
444 weightCovNoB, _) = fitNoB.getFitData(i, j, divideByMu=
True, returnMasked=
True)
445 mueNoB += list(muNoB)
446 reseNoB += list(covNoB - modelNoB)
447 wceNoB += list(weightCovNoB)
450 fit_curve, = plt.plot(mu, model + counter*offset,
'-', linewidth=4.0)
454 xb, yb, wyb, sigyb = self.
binData(mu, cov, gind, weightCov)
455 chi2bin = (sigyb*wyb).mean()
456 plt.errorbar(xb, yb+counter*offset, yerr=sigyb, marker=
'o', linestyle=
'none', markersize=6.5,
457 color=fit_curve.get_color(), label=f
"{amp} (N: {len(mu)})")
460 points, = plt.plot(mu, cov + counter*offset,
'.', color=fit_curve.get_color())
461 plt.legend(loc=
'upper right', fontsize=8)
462 aij = fit.getA()[i, j]
463 bij = fit.getB()[i, j]
466 if fit.getACov()
is not None:
467 lcov.append(fit.getACov()[i, j, i, j])
471 log.info(
'Cov%d%d %s: slope %g b %g chi2 %f chi2bin %f'%(i, j, amp, aij, bij, chi2, chi2bin))
475 lcov = np.array(lcov)
476 lchi2 = np.array(lchi2)
478 rese = np.array(rese)
480 mueNoB = np.array(mueNoB)
481 reseNoB = np.array(reseNoB)
482 wceNoB = np.array(wceNoB)
484 plt.xlabel(
r"$\mu (el)$", fontsize=
'x-large')
485 plt.ylabel(
r"$Cov{%d%d}/\mu + Cst (el)$"%(i, j), fontsize=
'x-large')
488 xb, yb, wyb, sigyb = self.
binData(mue, rese, gind, wce)
490 ax1 = plt.subplot(gs[1], sharex=ax0)
491 ax1.errorbar(xb, yb, yerr=sigyb, marker=
'o', linestyle=
'none', label=
'Full fit')
493 xb2, yb2, wyb2, sigyb2 = self.
binData(mueNoB, reseNoB, gindNoB, wceNoB)
495 ax1.errorbar(xb2, yb2, yerr=sigyb2, marker=
'o', linestyle=
'none', label=
'b = 0')
496 ax1.tick_params(axis=
'both', labelsize=
'x-large')
497 plt.legend(loc=
'upper left', fontsize=
'large')
499 plt.plot(xb, [0]*len(xb),
'--', color=
'k')
500 plt.ticklabel_format(style=
'sci', axis=
'x', scilimits=(0, 0))
501 plt.ticklabel_format(style=
'sci', axis=
'y', scilimits=(0, 0))
502 plt.xlabel(
r'$\mu (el)$', fontsize=
'x-large')
503 plt.ylabel(
r'$Cov{%d%d}/\mu$ -model (el)'%(i, j), fontsize=
'x-large')
505 plt.suptitle(f
"Nbins: {numberOfBins}")
508 labels0 = [item.get_text()
for item
in ax0.get_yticklabels()]
510 ax0.set_yticklabels(labels0)
511 pdfPages.savefig(fig)
517 """Fig. 12 of Astier+19
519 Color display of a and b arrays fits, averaged over channels.
524 Dictionary of CovFit objects, with amp names as keys.
526 pdfPages: `matplotlib.backends.backend_pdf.PdfPages`
527 PDF file where the plots will be saved.
530 Maximum lag for b arrays.
533 for amp, fit
in covFits.items():
536 a = np.array(a).mean(axis=0)
537 b = np.array(b).mean(axis=0)
538 fig = plt.figure(figsize=(7, 11))
539 ax0 = fig.add_subplot(2, 1, 1)
540 im0 = ax0.imshow(np.abs(a.transpose()), origin=
'lower', norm=mpl.colors.LogNorm())
541 ax0.tick_params(axis=
'both', labelsize=
'x-large')
542 ax0.set_title(
r'$|a|$', fontsize=
'x-large')
543 ax0.xaxis.set_ticks_position(
'bottom')
544 cb0 = plt.colorbar(im0)
545 cb0.ax.tick_params(labelsize=
'x-large')
547 ax1 = fig.add_subplot(2, 1, 2)
548 ax1.tick_params(axis=
'both', labelsize=
'x-large')
549 ax1.yaxis.set_major_locator(MaxNLocator(integer=
True))
550 ax1.xaxis.set_major_locator(MaxNLocator(integer=
True))
551 im1 = ax1.imshow(1e6*b[:bRange, :bRange].transpose(), origin=
'lower')
552 cb1 = plt.colorbar(im1)
553 cb1.ax.tick_params(labelsize=
'x-large')
554 ax1.set_title(
r'$b \times 10^6$', fontsize=
'x-large')
555 ax1.xaxis.set_ticks_position(
'bottom')
557 pdfPages.savefig(fig)
563 """Fig. 13 of Astier+19.
565 Values of a and b arrays fits, averaged over amplifiers, as a function of distance.
570 Dictionary of CovFit objects, with amp names as keys.
572 pdfPages: `matplotlib.backends.backend_pdf.PdfPages`
573 PDF file where the plots will be saved.
576 Maximum lag for b arrays.
578 a = np.array([f.getA()
for f
in covFits.values()])
580 sy = a.std(axis=0)/np.sqrt(len(covFits))
581 i, j = np.indices(y.shape)
582 upper = (i >= j).ravel()
583 r = np.sqrt(i**2 + j**2).ravel()
586 fig = plt.figure(figsize=(6, 9))
587 ax = fig.add_subplot(211)
588 ax.set_xlim([0.5, r.max()+1])
589 ax.errorbar(r[upper], y[upper], yerr=sy[upper], marker=
'o', linestyle=
'none', color=
'b',
591 ax.errorbar(r[~upper], y[~upper], yerr=sy[~upper], marker=
'o', linestyle=
'none', color=
'r',
593 ax.legend(loc=
'upper center', fontsize=
'x-large')
594 ax.set_xlabel(
r'$\sqrt{i^2+j^2}$', fontsize=
'x-large')
595 ax.set_ylabel(
r'$a_{ij}$', fontsize=
'x-large')
597 ax.tick_params(axis=
'both', labelsize=
'x-large')
600 axb = fig.add_subplot(212)
601 b = np.array([f.getB()
for f
in covFits.values()])
603 syb = b.std(axis=0)/np.sqrt(len(covFits))
604 ib, jb = np.indices(yb.shape)
605 upper = (ib > jb).ravel()
606 rb = np.sqrt(i**2 + j**2).ravel()
611 axb.set_xlim([xmin, xmax+0.2])
612 cutu = (r > xmin) & (r < xmax) & (upper)
613 cutl = (r > xmin) & (r < xmax) & (~upper)
614 axb.errorbar(rb[cutu], yb[cutu], yerr=syb[cutu], marker=
'o', linestyle=
'none', color=
'b',
616 axb.errorbar(rb[cutl], yb[cutl], yerr=syb[cutl], marker=
'o', linestyle=
'none', color=
'r',
618 plt.legend(loc=
'upper center', fontsize=
'x-large')
619 axb.set_xlabel(
r'$\sqrt{i^2+j^2}$', fontsize=
'x-large')
620 axb.set_ylabel(
r'$b_{ij}$', fontsize=
'x-large')
621 axb.ticklabel_format(style=
'sci', axis=
'y', scilimits=(0, 0))
622 axb.tick_params(axis=
'both', labelsize=
'x-large')
624 pdfPages.savefig(fig)
630 """Fig. 14. of Astier+19
632 Cumulative sum of a_ij as a function of maximum separation. This plot displays the average over
638 Dictionary of CovFit objects, with amp names as keys.
640 pdfPages: `matplotlib.backends.backend_pdf.PdfPages`
641 PDF file where the plots will be saved.
644 for amp, fit
in covFits.items():
647 a = np.array(a).mean(axis=0)
648 b = np.array(b).mean(axis=0)
649 fig = plt.figure(figsize=(7, 6))
650 w = 4*np.ones_like(a)
655 indices = range(1, a.shape[0]+1)
656 sums = [wa[0:n, 0:n].sum()
for n
in indices]
657 ax = plt.subplot(111)
658 ax.plot(indices, sums/sums[0],
'o', color=
'b')
660 ax.set_xlim(indices[0]-0.5, indices[-1]+0.5)
661 ax.set_ylim(
None, 1.2)
662 ax.set_ylabel(
r'$[\sum_{|i|<n\ &\ |j|<n} a_{ij}] / |a_{00}|$', fontsize=
'x-large')
663 ax.set_xlabel(
'n', fontsize=
'x-large')
664 ax.tick_params(axis=
'both', labelsize=
'x-large')
666 pdfPages.savefig(fig)
672 """Fig. 15 in Astier+19.
674 Illustrates systematic bias from estimating 'a'
675 coefficients from the slope of correlations as opposed to the
676 full model in Astier+19.
681 Dictionary of CovFit objects, with amp names as keys.
684 Dictionary of CovFit objects, with amp names as keys (b=0 in Eq. 20 of Astier+19).
686 signalElectrons : `float`
687 Signal at which to evaluate the a_ij coefficients.
689 pdfPages: `matplotlib.backends.backend_pdf.PdfPages`
690 PDF file where the plots will be saved.
692 maxr : `int`, optional
696 fig = plt.figure(figsize=(7, 11))
697 title = [f
"'a' relative bias at {signalElectrons} e",
"'a' relative bias (b=0)"]
698 data = [covFits, covFitsNoB]
703 for fit
in data[k].values():
709 diffs.append((aOld-a))
710 amean = np.array(amean).mean(axis=0)
711 diff = np.array(diffs).mean(axis=0)
717 diff = diff[:maxr, :maxr]
718 ax0 = fig.add_subplot(2, 1, k+1)
719 im0 = ax0.imshow(diff.transpose(), origin=
'lower')
720 ax0.yaxis.set_major_locator(MaxNLocator(integer=
True))
721 ax0.xaxis.set_major_locator(MaxNLocator(integer=
True))
722 ax0.tick_params(axis=
'both', labelsize=
'x-large')
724 ax0.set_title(title[k])
727 pdfPages.savefig(fig)
731 def _plotStandardPtc(self, dataset, ptcFitType, pdfPages):
732 """Plot PTC, var/signal vs signal, linearity, and linearity residual per amplifier.
736 dataset : `lsst.cp.pipe.ptc.PhotonTransferCurveDataset`
737 The dataset containing the means, variances, exposure times, and mask.
740 Type of the model fit to the PTC. Options: 'FULLCOVARIANCE', EXPAPPROXIMATION, or 'POLYNOMIAL'.
742 pdfPages: `matplotlib.backends.backend_pdf.PdfPages`
743 PDF file where the plots will be saved.
746 if ptcFitType ==
'EXPAPPROXIMATION':
748 stringTitle = (
r"Var = $\frac{1}{2g^2a_{00}}(\exp (2a_{00} \mu g) - 1) + \frac{n_{00}}{g^2}$ ")
749 elif ptcFitType ==
'POLYNOMIAL':
750 ptcFunc = funcPolynomial
751 for key
in dataset.ptcFitPars:
752 deg = len(dataset.ptcFitPars[key]) - 1
754 stringTitle =
r"Polynomial (degree: %g)" % (deg)
756 raise RuntimeError(f
"The input dataset had an invalid dataset.ptcFitType: {ptcFitType}. \n" +
757 "Options: 'FULLCOVARIANCE', EXPAPPROXIMATION, or 'POLYNOMIAL'.")
762 supTitleFontSize = 18
766 nAmps = len(dataset.ampNames)
769 nRows = np.sqrt(nAmps)
770 mantissa, _ = np.modf(nRows)
772 nRows = int(nRows) + 1
778 f, ax = plt.subplots(nrows=nRows, ncols=nCols, sharex=
'col', sharey=
'row', figsize=(13, 10))
779 f2, ax2 = plt.subplots(nrows=nRows, ncols=nCols, sharex=
'col', sharey=
'row', figsize=(13, 10))
780 f3, ax3 = plt.subplots(nrows=nRows, ncols=nCols, sharex=
'col', sharey=
'row', figsize=(13, 10))
782 for i, (amp, a, a2, a3)
in enumerate(zip(dataset.ampNames, ax.flatten(), ax2.flatten(),
784 meanVecOriginal = np.array(dataset.rawMeans[amp])
785 varVecOriginal = np.array(dataset.rawVars[amp])
786 mask = dataset.visitMask[amp]
788 a.set_title(f
"{amp} (BAD)", fontsize=titleFontSize)
789 a2.set_title(f
"{amp} (BAD)", fontsize=titleFontSize)
790 a3.set_title(f
"{amp} (BAD)", fontsize=titleFontSize)
792 meanVecFinal = meanVecOriginal[mask]
793 varVecFinal = varVecOriginal[mask]
794 meanVecOutliers = meanVecOriginal[np.invert(mask)]
795 varVecOutliers = varVecOriginal[np.invert(mask)]
796 pars, parsErr = dataset.ptcFitPars[amp], dataset.ptcFitParsError[amp]
797 ptcRedChi2 = dataset.ptcFitReducedChiSquared[amp]
798 if ptcFitType ==
'EXPAPPROXIMATION':
799 if len(meanVecFinal):
800 ptcA00, ptcA00error = pars[0], parsErr[0]
801 ptcGain, ptcGainError = pars[1], parsErr[1]
802 ptcNoise = np.sqrt((pars[2]))
803 ptcNoiseAdu = ptcNoise*(1./ptcGain)
804 ptcNoiseError = 0.5*(parsErr[2]/np.fabs(pars[2]))*np.sqrt(np.fabs(pars[2]))
805 stringLegend = (f
"a00: {ptcA00:.2e}+/-{ptcA00error:.2e} 1/e"
806 f
"\nGain: {ptcGain:.4}+/-{ptcGainError:.2e} e/DN"
807 f
"\nNoise: {ptcNoise:.4}+/-{ptcNoiseError:.2e} e\n"
808 r"$\chi^2_{\rm{red}}$: " + f
"{ptcRedChi2:.4}")
810 if ptcFitType ==
'POLYNOMIAL':
811 if len(meanVecFinal):
812 ptcGain, ptcGainError = 1./pars[1], np.fabs(1./pars[1])*(parsErr[1]/pars[1])
813 ptcNoiseAdu = np.sqrt((pars[0]))
814 ptcNoise = ptcNoiseAdu*ptcGain
815 ptcNoiseError = (0.5*(parsErr[0]/np.fabs(pars[0]))*(np.sqrt(np.fabs(pars[0]))))*ptcGain
816 stringLegend = (f
"Gain: {ptcGain:.4}+/-{ptcGainError:.2e} e/DN\n"
817 f
"Noise: {ptcNoise:.4}+/-{ptcNoiseError:.2e} e\n"
818 r"$\chi^2_{\rm{red}}$: " + f
"{ptcRedChi2:.4}")
820 a.set_xlabel(
r'Mean signal ($\mu$, DN)', fontsize=labelFontSize)
821 a.set_ylabel(
r'Variance (DN$^2$)', fontsize=labelFontSize)
822 a.tick_params(labelsize=11)
823 a.set_xscale(
'linear', fontsize=labelFontSize)
824 a.set_yscale(
'linear', fontsize=labelFontSize)
826 a2.set_xlabel(
r'Mean Signal ($\mu$, DN)', fontsize=labelFontSize)
827 a2.set_ylabel(
r'Variance (DN$^2$)', fontsize=labelFontSize)
828 a2.tick_params(labelsize=11)
832 a3.set_xlabel(
r'Mean signal ($\mu$, DN)', fontsize=labelFontSize)
833 a3.set_ylabel(
r'Variance/$\mu$ (DN)', fontsize=labelFontSize)
834 a3.tick_params(labelsize=11)
835 a3.set_xscale(
'linear', fontsize=labelFontSize)
836 a3.set_yscale(
'linear', fontsize=labelFontSize)
838 minMeanVecFinal = np.min(meanVecFinal)
839 maxMeanVecFinal = np.max(meanVecFinal)
840 meanVecFit = np.linspace(minMeanVecFinal, maxMeanVecFinal, 100*len(meanVecFinal))
841 minMeanVecOriginal = np.min(meanVecOriginal)
842 maxMeanVecOriginal = np.max(meanVecOriginal)
843 deltaXlim = maxMeanVecOriginal - minMeanVecOriginal
845 a.plot(meanVecFit, ptcFunc(pars, meanVecFit), color=
'red')
846 a.plot(meanVecFinal, ptcNoiseAdu**2 + (1./ptcGain)*meanVecFinal, color=
'green',
848 a.scatter(meanVecFinal, varVecFinal, c=
'blue', marker=
'o', s=markerSize)
849 a.scatter(meanVecOutliers, varVecOutliers, c=
'magenta', marker=
's', s=markerSize)
850 a.text(0.03, 0.7, stringLegend, transform=a.transAxes, fontsize=legendFontSize)
851 a.set_title(amp, fontsize=titleFontSize)
852 a.set_xlim([minMeanVecOriginal - 0.2*deltaXlim, maxMeanVecOriginal + 0.2*deltaXlim])
855 a2.plot(meanVecFit, ptcFunc(pars, meanVecFit), color=
'red')
856 a2.scatter(meanVecFinal, varVecFinal, c=
'blue', marker=
'o', s=markerSize)
857 a2.scatter(meanVecOutliers, varVecOutliers, c=
'magenta', marker=
's', s=markerSize)
858 a2.text(0.03, 0.7, stringLegend, transform=a2.transAxes, fontsize=legendFontSize)
859 a2.set_title(amp, fontsize=titleFontSize)
860 a2.set_xlim([minMeanVecOriginal, maxMeanVecOriginal])
863 a3.plot(meanVecFit, ptcFunc(pars, meanVecFit)/meanVecFit, color=
'red')
864 a3.scatter(meanVecFinal, varVecFinal/meanVecFinal, c=
'blue', marker=
'o', s=markerSize)
865 a3.scatter(meanVecOutliers, varVecOutliers/meanVecOutliers, c=
'magenta', marker=
's',
867 a3.text(0.2, 0.65, stringLegend, transform=a3.transAxes, fontsize=legendFontSize)
868 a3.set_title(amp, fontsize=titleFontSize)
869 a3.set_xlim([minMeanVecOriginal - 0.2*deltaXlim, maxMeanVecOriginal + 0.2*deltaXlim])
871 f.suptitle(
"PTC \n Fit: " + stringTitle, fontsize=supTitleFontSize)
873 f2.suptitle(
"PTC (log-log)", fontsize=supTitleFontSize)
875 f3.suptitle(
r"Var/$\mu$", fontsize=supTitleFontSize)
880 def _plotLinearizer(self, dataset, linearizer, pdfPages):
881 """Plot linearity and linearity residual per amplifier
885 dataset : `lsst.cp.pipe.ptc.PhotonTransferCurveDataset`
886 The dataset containing the means, variances, exposure times, and mask.
888 linearizer : `lsst.ip.isr.Linearizer`
894 supTitleFontSize = 18
897 nAmps = len(dataset.ampNames)
900 nRows = np.sqrt(nAmps)
901 mantissa, _ = np.modf(nRows)
903 nRows = int(nRows) + 1
910 f, ax = plt.subplots(nrows=nRows, ncols=nCols, sharex=
'col', sharey=
'row', figsize=(13, 10))
911 f2, ax2 = plt.subplots(nrows=nRows, ncols=nCols, sharex=
'col', sharey=
'row', figsize=(13, 10))
912 for i, (amp, a, a2)
in enumerate(zip(dataset.ampNames, ax.flatten(), ax2.flatten())):
913 mask = dataset.visitMask[amp]
915 a.set_title(f
"{amp} (BAD)", fontsize=titleFontSize)
916 a2.set_title(f
"{amp} (BAD)", fontsize=titleFontSize)
918 meanVecFinal = np.array(dataset.rawMeans[amp])[mask]
919 timeVecFinal = np.array(dataset.rawExpTimes[amp])[mask]
921 a.set_xlabel(
'Time (sec)', fontsize=labelFontSize)
922 a.set_ylabel(
r'Mean signal ($\mu$, DN)', fontsize=labelFontSize)
923 a.tick_params(labelsize=labelFontSize)
924 a.set_xscale(
'linear', fontsize=labelFontSize)
925 a.set_yscale(
'linear', fontsize=labelFontSize)
927 a2.axhline(y=0, color=
'k')
928 a2.axvline(x=0, color=
'k', linestyle=
'-')
929 a2.set_xlabel(
r'Mean signal ($\mu$, DN)', fontsize=labelFontSize)
930 a2.set_ylabel(
'Fractional nonlinearity (%)', fontsize=labelFontSize)
931 a2.tick_params(labelsize=labelFontSize)
932 a2.set_xscale(
'linear', fontsize=labelFontSize)
933 a2.set_yscale(
'linear', fontsize=labelFontSize)
935 pars, parsErr = linearizer.fitParams[amp], linearizer.fitParamsErr[amp]
936 k0, k0Error = pars[0], parsErr[0]
937 k1, k1Error = pars[1], parsErr[1]
938 k2, k2Error = pars[2], parsErr[2]
939 linRedChi2 = linearizer.fitChiSq[amp]
940 stringLegend = (f
"k0: {k0:.4}+/-{k0Error:.2e} DN\nk1: {k1:.4}+/-{k1Error:.2e} DN/t"
941 f
"\nk2: {k2:.2e}+/-{k2Error:.2e} DN/t^2\n"
942 r"$\chi^2_{\rm{red}}$: " + f
"{linRedChi2:.4}")
943 a.scatter(timeVecFinal, meanVecFinal)
944 a.plot(timeVecFinal,
funcPolynomial(pars, timeVecFinal), color=
'red')
945 a.text(0.03, 0.75, stringLegend, transform=a.transAxes, fontsize=legendFontSize)
946 a.set_title(f
"{amp}", fontsize=titleFontSize)
948 linearPart = k0 + k1*timeVecFinal
949 fracLinRes = 100*(linearPart - meanVecFinal)/linearPart
950 a2.plot(meanVecFinal, fracLinRes, c=
'g')
951 a2.set_title(f
"{amp}", fontsize=titleFontSize)
953 f.suptitle(
"Linearity \n Fit: Polynomial (degree: %g)"
955 fontsize=supTitleFontSize)
956 f2.suptitle(
r"Fractional NL residual" +
"\n" +
957 r"$100\times \frac{(k_0 + k_1*Time-\mu)}{k_0+k_1*Time}$",
958 fontsize=supTitleFontSize)
964 """Group data into bins, with at most maxDiff distance between bins.
972 Maximum distance between bins.
981 index = np.zeros_like(x, dtype=np.int32)
986 for i
in range(1, len(ix)):
988 if (xval - xc < maxDiff):
989 xc = (ng*xc + xval)/(ng+1)
1002 """Builds an index with regular binning. The result can be fed into binData.
1013 np.digitize(x, bins): `numpy.array`
1017 bins = np.linspace(x.min(), x.max() + abs(x.max() * 1e-7), nBins + 1)
1018 return np.digitize(x, bins)
1022 """Bin data (usually for display purposes).
1033 Bin number of each datum.
1036 Inverse rms of each datum to use when averaging (the actual weight is wy**2).
1047 wybin: `numpy.array`
1048 Binned weights in y, computed from wy's in each bin.
1050 sybin: `numpy.array`
1051 Uncertainty on the bin average, considering actual scatter, and ignoring weights.
1055 wy = np.ones_like(x)
1056 binIndexSet = set(binIndex)
1059 xbin = np.array([xw2[binIndex == i].sum()/w2[binIndex == i].sum()
for i
in binIndexSet])
1062 ybin = np.array([yw2[binIndex == i].sum()/w2[binIndex == i].sum()
for i
in binIndexSet])
1064 wybin = np.sqrt(np.array([w2[binIndex == i].sum()
for i
in binIndexSet]))
1065 sybin = np.array([y[binIndex == i].
std()/np.sqrt(np.array([binIndex == i]).sum())
1066 for i
in binIndexSet])
1068 return xbin, ybin, wybin, sybin