Coverage for python/lsst/validate/drp/plot.py: 8%
264 statements
« prev ^ index » next coverage.py v7.1.0, created at 2023-02-05 19:03 -0800
« prev ^ index » next coverage.py v7.1.0, created at 2023-02-05 19:03 -0800
1# LSST Data Management System
2# Copyright 2008-2016 AURA/LSST.
3#
4# This product includes software developed by the
5# LSST Project (http://www.lsst.org/).
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation, either version 3 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the LSST License Statement and
18# the GNU General Public License along with this program. If not,
19# see <https://www.lsstcorp.org/LegalNotices/>.
20"""Matplotlib plots describing lsst.validate.drp metric measurements, as well
21as analytic models of photometric and astrometric repeatability.
22"""
24__all__ = ['plotOutlinedAxline',
25 'plotAstrometryErrorModel',
26 'plotAstromErrModelFit', 'plotPhotErrModelFit',
27 'plotPhotometryErrorModel', 'plotPA1', 'plotAMx']
30import matplotlib.pylab as plt
31import numpy as np
32import astropy.units as u
33import scipy.stats
34from .astromerrmodel import astromErrModel
35from .photerrmodel import photErrModel
36from lsst.verify import Name
39# Plotting defaults
40plt.rcParams['axes.linewidth'] = 2
41plt.rcParams['mathtext.default'] = 'regular'
42plt.rcParams['font.size'] = 20
43plt.rcParams['axes.labelsize'] = 20
44# plt.rcParams['figure.titlesize'] = 30
46color = {'all': 'grey', 'bright': 'blue',
47 'iqr': 'green', 'rms': 'red'}
50def makeFilename(prefix, formatStr, **kwargs):
51 """Return a filename for writing to.
53 Return prefix_formatStr.format(kwargs) if prefix is not empty, otherwise
54 just return formatStr.format(kwargs).
55 """
56 formatted = formatStr.format(**kwargs)
57 if prefix is None or prefix == "":
58 return formatted
59 else:
60 return "{}_{}".format(prefix, formatted)
63def plotOutlinedAxline(axMethod, x, **kwargs):
64 """Plot an axis line with a white shadow for better contrast.
66 Parameters
67 ----------
68 axMethod : `matplotlib.pyplot.axhline` or `matplotlib.pyplot.axvline`
69 A horizontal or vertical axis line plotting function.
70 x : float
71 Axis coordinate
72 **kwargs :
73 Keyword arguments for `~matplotlib.pyplot.axhline` or
74 `~matplotlib.pyplot.axvline`.
75 """
76 shadowArgs = dict(kwargs)
77 foregroundArgs = dict(kwargs)
79 if 'linewidth' not in foregroundArgs:
80 foregroundArgs['linewidth'] = 3
82 if 'linewidth' in shadowArgs:
83 shadowArgs['linewidth'] += 1
84 else:
85 shadowArgs['linewidth'] = 4
86 shadowArgs['color'] = 'w'
87 shadowArgs['label'] = None
89 axMethod(x, **shadowArgs)
90 axMethod(x, **foregroundArgs)
93def plotAstrometryErrorModel(dataset, astromModel, outputPrefix=''):
94 """Plot angular distance between matched sources from different exposures.
96 Creates a file containing the plot with a filename beginning with
97 `outputPrefix`.
99 Parameters
100 ----------
101 dataset : `lsst.verify.Blob`
102 Blob with the multi-visit photometry model.
103 photomModel : `lsst.verify.Blob`
104 A `Blob` containing the analytic photometry model.
105 outputPrefix : str, optional
106 Prefix to use for filename of plot file. Will also be used in plot
107 titles. E.g., ``outputPrefix='Cfht_output_r_'`` will result in a file
108 named ``'Cfht_output_r_check_astrometry.png'``.
109 """
110 bright, = np.where(dataset['snr'].quantity > astromModel['brightSnrMin'].quantity)
112 dist = dataset['dist'].quantity
113 numMatched = len(dist)
114 dist_median = np.median(dist)
115 bright_dist_median = np.median(dist[bright])
117 fig, ax = plt.subplots(ncols=2, nrows=1, figsize=(18, 12))
119 ax[0].hist(dist, bins=100, color=color['all'],
120 histtype='stepfilled', orientation='horizontal')
121 if len(dist[bright]):
122 ax[0].hist(dist[bright], bins=100, color=color['bright'],
123 histtype='stepfilled', orientation='horizontal')
125 ax[0].set_ylim([0., 500.])
126 ax[0].set_ylabel("Distance [{unit:latex}]".format(unit=dist.unit))
127 plotOutlinedAxline(
128 ax[0].axhline,
129 dist_median.value,
130 color=color['all'],
131 label="Median RMS: {v.value:.1f} {v.unit:latex}".format(v=dist_median))
132 plotOutlinedAxline(
133 ax[0].axhline,
134 bright_dist_median.value,
135 color=color['bright'],
136 label="SNR > {snr:.0f}\nMedian RMS: {v.value:.1f} {v.unit:latex}".format(
137 snr=astromModel['brightSnrMin'].quantity.value,
138 v=bright_dist_median))
139 ax[0].legend(loc='upper right')
141 snr = dataset['snr'].quantity
142 ax[1].scatter(snr, dist,
143 s=10, color=color['all'], label='All')
144 ax[1].scatter(snr[bright], dist[bright], s=10,
145 color=color['bright'],
146 label='SNR > {0:.0f}'.format(astromModel['brightSnrMin'].quantity.value))
147 ax[1].set_xlabel("SNR")
148 ax[1].set_xscale("log")
149 ax[1].set_ylim([0., 500.])
150 matchCountTemplate = '\n'.join([
151 'Matches:',
152 '{nBright:d} high SNR,',
153 '{nAll:d} total'])
154 ax[1].text(0.6, 0.6, matchCountTemplate.format(nBright=len(bright),
155 nAll=numMatched),
156 transform=ax[1].transAxes, ha='left', va='baseline')
158 w, = np.where(dist < 200 * u.marcsec)
159 plotAstromErrModelFit(snr[w], dist[w], astromModel,
160 ax=ax[1])
162 ax[1].legend(loc='upper right')
163 ax[1].axvline(astromModel['brightSnrMin'].quantity,
164 color='red', linewidth=4, linestyle='dashed')
165 plotOutlinedAxline(
166 ax[0].axhline,
167 dist_median.value,
168 color=color['all'])
169 plotOutlinedAxline(
170 ax[0].axhline,
171 bright_dist_median.value,
172 color=color['bright'])
174 # Using title rather than suptitle because I can't get the top padding
175 plt.suptitle("Astrometry Check : %s" % outputPrefix,
176 fontsize=30)
177 ext = 'png'
178 pathFormat = "{name}.{ext}"
179 plotPath = makeFilename(outputPrefix, pathFormat, name="check_astrometry", ext=ext)
180 plt.savefig(plotPath, format=ext)
181 plt.close(fig)
182 print("Wrote plot:", plotPath)
185def plotAstromErrModelFit(snr, dist, model,
186 color='red', ax=None, verbose=True):
187 """Plot model of photometric error from LSST Overview paper
188 http://arxiv.org/abs/0805.2366v4
190 Astrometric Errors
191 error = C * theta / SNR
193 Parameters
194 ----------
195 snr : list or numpy.array
196 S/N of photometric measurements
197 dist : list or numpy.array
198 Separation from reference [mas]
199 model : `lsst.verify.Blob`
200 A `Blob` holding the analytic astrometric model.
201 """
202 if ax is None:
203 ax = plt.figure()
204 xlim = [10, 30]
205 else:
206 xlim = ax.get_xlim()
208 x_model = np.logspace(np.log10(xlim[0]), np.log10(xlim[1]), num=100)
209 fit_model_mas_err = astromErrModel(x_model,
210 theta=model['theta'].quantity,
211 sigmaSys=model['sigmaSys'].quantity,
212 C=model['C'].quantity)
213 ax.plot(x_model, fit_model_mas_err,
214 color=color, linewidth=2,
215 label='Model')
217 modelLabelTemplate = '\n'.join([
218 r'$C = {C:.2g}$',
219 r'$\theta$ = {theta:.4g}',
220 r'$\sigma_\mathrm{{sys}}$ = {sigmaSys.value:.2g} {sigmaSys.unit:latex}'])
221 modelLabel = modelLabelTemplate.format(
222 C=model['C'].quantity,
223 theta=model['theta'].quantity,
224 sigmaSys=model['sigmaSys'].quantity)
225 ax.text(0.6, 0.4, modelLabel,
226 transform=ax.transAxes, va='baseline', ha='left', color=color)
227 # Set the x limits back to their original values.
228 ax.set_xlim(xlim)
231def plotPhotErrModelFit(mag, mmag_err, photomModel, color='red', ax=None,
232 verbose=True):
233 """Plot model of photometric error from LSST Overview paper (Eq. 4 & 5)
235 Parameters
236 ----------
237 mag : list or numpy.array
238 Magnitude
239 mmag_err : list or numpy.array
240 Magnitude uncertainty or variation in *mmag*.
241 photomModel : `lsst.verify.Blob`
242 A `Blob` holding the parameters to display.
243 ax : matplotlib.Axis, optional
244 The Axis object to plot to.
245 verbose : bool, optional
246 Produce extra output to STDOUT
247 """
249 if ax is None:
250 ax = plt.figure()
251 xlim = [10, 30]
252 else:
253 xlim = ax.get_xlim()
255 x_model = np.linspace(*xlim, num=100)
256 fit_model_mag_err = photErrModel(x_model,
257 sigmaSys=photomModel['sigmaSys'].quantity.to(u.mag).value,
258 gamma=photomModel['gamma'].quantity.value,
259 m5=photomModel['m5'].quantity.to(u.mag).value)
260 fit_model_mag_err = fit_model_mag_err * u.mag
261 ax.plot(x_model, fit_model_mag_err.to(u.mmag).value,
262 color=color, linewidth=2,
263 label='Model')
265 labelFormatStr = '\n'.join([
266 r'$\sigma_\mathrm{{sys}}$ = {sigmaSysMmag:.4f} mmag',
267 r'$\gamma = {gamma:.4f}$',
268 r'$m_5 =$ {m5:.4f}'])
269 label = labelFormatStr.format(sigmaSysMmag=1000*photomModel['sigmaSys'].quantity.to(u.mag).value,
270 gamma=photomModel['gamma'].quantity.value,
271 m5=photomModel['m5'].quantity.value)
272 ax.text(0.1, 0.8, label, color=color,
273 transform=ax.transAxes, ha='left', va='top')
276def plotPhotometryErrorModel(dataset, photomModel,
277 filterName='', outputPrefix=''):
278 """Plot photometric RMS for matched sources.
280 Parameters
281 ----------
282 dataset : `lsst.verify.Blob`
283 A `Blob` with the multi-visit photometry model.
284 photomModel : `lsst.verify.Blob`
285 A `Blob` holding the analytic photometry model parameters.
286 filterName : str, optional
287 Name of the observed filter to use on axis labels.
288 outputPrefix : str, optional
289 Prefix to use for filename of plot file. Will also be used in plot
290 titles. E.g., ``outputPrefix='Cfht_output_r_'`` will result in a file
291 named ``'Cfht_output_r_check_photometry.png'``.
292 """
293 bright, = np.where(dataset['snr'].quantity > photomModel['brightSnrMin'].quantity)
295 numMatched = len(dataset['mag'].quantity)
296 magrms = dataset['magrms'].quantity
297 mmagRms = magrms.to(u.mmag)
298 mmagRmsHighSnr = mmagRms[bright]
299 magerr = dataset['magerr'].quantity
300 mmagErr = magerr.to(u.mmag)
301 mmagErrHighSnr = mmagErr[bright]
303 mmagrms_median = np.median(mmagRms)
304 bright_mmagrms_median = np.median(mmagRmsHighSnr)
306 fig, ax = plt.subplots(ncols=2, nrows=2, figsize=(18, 16))
308 ax[0][0].hist(mmagRms,
309 bins=100, range=(0, 500), color=color['all'],
310 histtype='stepfilled', orientation='horizontal')
311 if len(mmagRmsHighSnr):
312 ax[0][0].hist(mmagRmsHighSnr,
313 bins=100, range=(0, 500),
314 color=color['bright'],
315 histtype='stepfilled', orientation='horizontal')
316 plotOutlinedAxline(
317 ax[0][0].axhline,
318 mmagrms_median.value,
319 color=color['all'],
320 label="Median RMS: {v:.1f}".format(v=mmagrms_median))
321 plotOutlinedAxline(
322 ax[0][0].axhline,
323 bright_mmagrms_median.value,
324 color=color['bright'],
325 label="SNR > {snr:.0f}\nMedian RMS: {v:.1f}".format(
326 snr=photomModel['brightSnrMin'].quantity.value,
327 v=bright_mmagrms_median))
329 ax[0][0].set_ylim([0, 500])
330 ax[0][0].set_ylabel("{magrms.label} [{mmagrms.unit:latex}]".format(
331 magrms=dataset['magrms'], mmagrms=mmagRms))
332 ax[0][0].legend(loc='upper right')
333 mag = dataset['mag'].quantity
334 ax[0][1].scatter(mag, mmagRms,
335 s=10, color=color['all'], label='All')
336 ax[0][1].scatter(mag[bright], mmagRmsHighSnr,
337 s=10, color=color['bright'],
338 label='{label} > {value:.0f}'.format(
339 label=photomModel['brightSnrMin'].label,
340 value=photomModel['brightSnrMin'].quantity.value))
341 ax[0][1].set_xlabel("{label} [{unit:latex}]".format(label=filterName,
342 unit=mag.unit))
343 ax[0][1].set_ylabel("{label} [{unit:latex}]".format(label=dataset['magrms'].label,
344 unit=mmagRmsHighSnr.unit))
345 ax[0][1].set_xlim([17, 24])
346 ax[0][1].set_ylim([0, 500])
347 ax[0][1].legend(loc='upper left')
348 plotOutlinedAxline(
349 ax[0][1].axhline,
350 mmagrms_median.value,
351 color=color['all'])
352 plotOutlinedAxline(
353 ax[0][1].axhline,
354 bright_mmagrms_median.value,
355 color=color['bright'])
356 matchCountTemplate = '\n'.join([
357 'Matches:',
358 '{nBright:d} high SNR,',
359 '{nAll:d} total'])
360 ax[0][1].text(0.1, 0.6, matchCountTemplate.format(nBright=len(bright),
361 nAll=numMatched),
362 transform=ax[0][1].transAxes, ha='left', va='top')
364 ax[1][0].scatter(mmagRms, mmagErr,
365 s=10, color=color['all'], label=None)
366 ax[1][0].scatter(mmagRmsHighSnr, mmagErrHighSnr,
367 s=10, color=color['bright'],
368 label=None)
369 ax[1][0].set_xscale('log')
370 ax[1][0].set_yscale('log')
371 ax[1][0].plot([0, 1000], [0, 1000],
372 linestyle='--', color='black', linewidth=2)
373 ax[1][0].set_xlabel("{label} [{unit:latex}]".format(
374 label=dataset['magrms'].label,
375 unit=mmagRms.unit))
376 ax[1][0].set_ylabel("Median Reported Magnitude Err [{unit:latex}]".format(
377 unit=mmagErr.unit))
379 brightSnrMag = 2.5*np.log10(1 + (1/photomModel['brightSnrMin'].quantity.value)) * u.mag
380 label = r'$SNR > {snr:.0f} \equiv \sigma < {snrMag:0.1f}$'.format(
381 snr=photomModel['brightSnrMin'].quantity.value,
382 snrMag=brightSnrMag.to(u.mmag))
383 ax[1][0].axhline(brightSnrMag.to(u.mmag).value,
384 color='red', linewidth=4,
385 linestyle='dashed',
386 label=label)
387 ax[1][0].set_xlim([1, 500])
388 ax[1][0].set_ylim([1, 500])
389 ax[1][0].legend(loc='upper center')
391 ax[1][1].scatter(mag, mmagErr,
392 color=color['all'], label=None)
393 ax[1][1].set_yscale('log')
394 ax[1][1].scatter(np.asarray(mag)[bright],
395 mmagErrHighSnr,
396 s=10, color=color['bright'],
397 label=None)
398 ax[1][1].set_xlabel("{name} [{unit:latex}]".format(
399 name=filterName, unit=mag.unit))
400 ax[1][1].set_ylabel("Median Reported Magnitude Err [{unit:latex}]".format(
401 unit=mmagErr.unit))
402 ax[1][1].set_xlim([17, 24])
403 ax[1][1].set_ylim([1, 500])
404 ax[1][1].axhline(brightSnrMag.to(u.mmag).value,
405 color='red', linewidth=4,
406 linestyle='dashed',
407 label=None)
409 w, = np.where(mmagErr < 200. * u.mmag)
410 plotPhotErrModelFit(mag[w].to(u.mag).value,
411 magerr[w].to(u.mmag).value,
412 photomModel, ax=ax[1][1])
413 ax[1][1].legend(loc='upper left')
415 plt.suptitle("Photometry Check : %s" % outputPrefix,
416 fontsize=30)
417 ext = 'png'
418 pathFormat = "{name}.{ext}"
419 plotPath = makeFilename(outputPrefix, pathFormat, name="check_photometry", ext=ext)
420 plt.savefig(plotPath, format=ext)
421 plt.close(fig)
422 print("Wrote plot:", plotPath)
425def plotPA1(pa1, outputPrefix=""):
426 """Plot the results of calculating the LSST SRC requirement PA1.
428 Creates a file containing the plot with a filename beginning with
429 `outputPrefix`.
431 Parameters
432 ----------
433 pa1 : `lsst.verify.Measurement`
434 A `Measurement` of the PA1 `Metric`.
435 outputPrefix : `str`, optional
436 Prefix to use for filename of plot file. Will also be used in plot
437 titles. E.g., outputPrefix='Cfht_output_r_' will result in a file
438 named ``'Cfht_output_r_AM1_D_5_arcmin_17.0-21.5.png'``
439 for an ``AMx.name=='AM1'`` and ``AMx.magRange==[17, 21.5]``.
440 """
441 diffRange = (-100, +100)
442 magDiff = pa1.extras['magDiff'].quantity
443 magMean = pa1.extras['magMean'].quantity
444 rms = pa1.extras['rms'].quantity
445 iqr = pa1.extras['iqr'].quantity
447 fig = plt.figure(figsize=(18, 12))
448 ax1 = fig.add_subplot(1, 2, 1)
449 ax1.scatter(magMean[0],
450 magDiff[0],
451 s=10, color=color['bright'], linewidth=0)
452 # index 0 because we show only the first sample from multiple trials
453 ax1.axhline(+rms[0].value, color=color['rms'], linewidth=3)
454 ax1.axhline(-rms[0].value, color=color['rms'], linewidth=3)
455 ax1.axhline(+iqr[0].value, color=color['iqr'], linewidth=3)
456 ax1.axhline(-iqr[0].value, color=color['iqr'], linewidth=3)
458 ax2 = fig.add_subplot(1, 2, 2, sharey=ax1)
459 ax2.hist(magDiff[0], bins=25, range=diffRange,
460 orientation='horizontal', histtype='stepfilled',
461 density=True, color=color['bright'])
462 ax2.set_xlabel("relative # / bin")
464 labelTemplate = r'PA1({label}) = {q.value:4.2f} {q.unit:latex}'
465 yv = np.linspace(diffRange[0], diffRange[1], 100)
466 ax2.plot(scipy.stats.norm.pdf(yv, scale=rms[0]), yv,
467 marker='', linestyle='-', linewidth=3, color=color['rms'],
468 label=labelTemplate.format(label='RMS', q=rms[0]))
469 ax2.plot(scipy.stats.norm.pdf(yv, scale=iqr[0]), yv,
470 marker='', linestyle='-', linewidth=3, color=color['iqr'],
471 label=labelTemplate.format(label='IQR', q=iqr[0]))
472 ax2.set_ylim(*diffRange)
473 ax2.legend()
474 ax1.set_xlabel("psf magnitude")
475 ax1.set_ylabel(r"psf magnitude diff ({0.unit:latex})".format(magDiff))
476 for label in ax2.get_yticklabels():
477 label.set_visible(False)
479 plt.tight_layout() # fix padding
480 ext = 'png'
481 pathFormat = "{name}.{ext}"
482 plotPath = makeFilename(outputPrefix, pathFormat, name="PA1", ext=ext)
483 plt.savefig(plotPath, format=ext)
484 plt.close(fig)
485 print("Wrote plot:", plotPath)
488def plotAMx(job, amx, afx, filterName, amxSpecName='design', outputPrefix=""):
489 """Plot a histogram of the RMS in relative distance between pairs of
490 stars.
492 Creates a file containing the plot with a filename beginning with
493 `outputPrefix`.
495 Parameters
496 ----------
497 job : `lsst.verify.Job`
498 `~lsst.verify.Job` providing access to metrics, specs and measurements
499 amx : `lsst.verify.Measurement`
500 afx : `lsst.verify.Measurement`
501 filterName : `str`
502 amxSpecName : `str`, optional
503 Name of the AMx specification to reference in the plot.
504 Default: ``'design'``.
505 outputPrefix : `str`, optional
506 Prefix to use for filename of plot file. Will also be used in plot
507 titles. E.g., ``outputPrefix='Cfht_output_r_'`` will result in a file
508 named ``'Cfht_output_r_AM1_D_5_arcmin_17.0-21.5.png'``
509 for an ``AMx.name=='AM1'`` and ``AMx.magRange==[17, 21.5]``.
510 """
511 if np.isnan(amx.quantity):
512 print("Skipping %s -- no measurement"%str(amx.metric_name))
513 return
515 fig = plt.figure(figsize=(10, 6))
516 ax1 = fig.add_subplot(1, 1, 1)
518 histLabelTemplate = 'D: [{inner.value:.1f}{inner.unit:latex}-{outer.value:.1f}{outer.unit:latex}]\n'\
519 'Mag: [{magBright:.1f}-{magFaint:.1f}]'
520 annulus = amx.extras['annulus'].quantity
521 magRange = amx.extras['magRange'].quantity
522 ax1.hist(amx.extras['rmsDistMas'].quantity, bins=25, range=(0.0, 100.0),
523 histtype='stepfilled',
524 label=histLabelTemplate.format(
525 inner=annulus[0],
526 outer=annulus[1],
527 magBright=magRange[0],
528 magFaint=magRange[1]))
529 metric_name = amx.metric_name
530 amxSpec = job.specs[Name(package=metric_name.package, metric=metric_name.metric, spec=amxSpecName)]
531 amxSpecLabelTemplate = '{amx.datum.label} {specname}: {amxSpec.threshold:.1f}'
532 amxSpecLabel = amxSpecLabelTemplate.format(
533 amx=amx,
534 specname=amxSpecName,
535 amxSpec=amxSpec)
536 ax1.axvline(amxSpec.threshold.value, 0, 1, linewidth=2, color='red',
537 label=amxSpecLabel)
539 if amxSpec.check(amx.quantity):
540 amxStatus = 'passed'
541 else:
542 amxStatus = 'failed'
543 amxLabelTemplate = '{amx.datum.label} measured: {amx.quantity:.1f} ({status})'
544 amxLabel = amxLabelTemplate.format(amx=amx, status=amxStatus)
545 ax1.axvline(amxSpec.threshold.value, 0, 1, linewidth=2, color='black',
546 label=amxLabel)
548 afxSpec = job.specs[Name(package=afx.metric_name.package, metric=afx.metric_name.metric, spec='srd')]
549 if afxSpec.check(afx.quantity):
550 afxStatus = 'passed'
551 else:
552 afxStatus = 'failed'
553 afxLabelTemplate = '{afx.datum.label} {afxSpec.name}: {afxSpec.threshold}%\n' + \
554 '{afx.datum.label} measured: {afx.quantity:.1f}% ({status})'
555 afxLabel = afxLabelTemplate.format(
556 afx=afx,
557 afxSpec=afxSpec,
558 status=afxStatus)
559 ax1.axvline((amx.quantity + afx.extras['ADx'].quantity).value,
560 0, 1, linewidth=2, color='green',
561 label=afxLabel)
563 title = '{metric} Astrometric Repeatability over {D.value:.0f}{D.unit:latex}'.format(
564 metric=amx.datum.label,
565 D=amx.extras['D'].quantity)
566 ax1.set_title(title)
567 ax1.set_xlim(0.0, 100.0)
568 ax1.set_xlabel(
569 '{rmsDistMas.label} ({unit})'.format(
570 rmsDistMas=amx.extras['rmsDistMas'], unit=amx.extras['rmsDistMas'].quantity.unit._repr_latex_()))
571 ax1.set_ylabel('# pairs / bin')
573 ax1.legend(loc='upper right', fontsize=16)
575 ext = 'png'
576 pathFormat = '{metric}_D_{D:d}_{Dunits}_' + \
577 '{magBright.value}_{magFaint.value}_{magFaint.unit}.{ext}'
578 plotPath = makeFilename(outputPrefix,
579 pathFormat,
580 metric=amx.datum.label,
581 D=int(amx.extras['D'].quantity.value),
582 Dunits=amx.extras['D'].quantity.unit,
583 magBright=magRange[0],
584 magFaint=magRange[1],
585 ext=ext)
587 plt.tight_layout() # fix padding
588 plt.savefig(plotPath, dpi=300, format=ext)
589 plt.close(fig)
590 print("Wrote plot:", plotPath)
593def plotTEx(job, tex, filterName, texSpecName='design', outputPrefix=''):
594 """Plot TEx correlation function measurements and thresholds.
596 Parameters
597 ----------
598 job : `lsst.verify.Job`
599 `Job` providing access to metrics, specs, and measurements
600 tex : `lsst.verify.Measurement
601 The ellipticity residual correlation `Measurement` object
602 filterName : str
603 Name of the filter of the images
604 texSpecName : str
605 Level of requirement to compare against.
606 Must be a into the metrics specified in the tex Measurement object
607 Typically one of 'design', 'minimum', 'stretch'
608 outputPrefix : str, optional
609 Prefix to use for filename of plot file.
611 Effects
612 -------
613 Saves an output plot file to that starts with specified outputPrefix.
615 """
616 fig = plt.figure(figsize=(10, 6))
617 ax1 = fig.add_subplot(1, 1, 1)
618 # Plot correlation vs. radius
619 radius = tex.extras['radius'].quantity
620 xip = tex.extras['xip'].quantity
621 xip_err = tex.extras['xip_err'].quantity
622 D = tex.extras['D'].quantity
623 bin_range_operator = tex.extras['bin_range_operator'].quantity
625 ax1.errorbar(radius.value, xip.value, yerr=xip_err.value)
626 ax1.set_xscale('log')
627 ax1.set_xlabel('Separation (arcmin)', size=19)
628 ax1.set_ylabel('Median Residual Ellipticity Correlation', size=19)
630 # Overlay requirements level
631 metric_name = tex.metric_name
632 texSpec = job.specs[Name(package=metric_name.package, metric=metric_name.metric, spec=texSpecName)]
633 texSpecLabel = '{tex.datum.label} {specname}: {texSpec:.2g}'.format(
634 tex=tex,
635 texSpec=texSpec.threshold,
636 specname=texSpecName)
637 ax1.axhline(texSpec.threshold.value, 0, 1, linewidth=2, color='red',
638 label=texSpecLabel)
640 # Overlay measured KPM whether it passed or failed.
641 if texSpec.check(tex.quantity):
642 texStatus = 'passed'
643 else:
644 texStatus = 'failed'
645 texLabelTemplate = '{tex.datum.label} measured: {tex.quantity:.2g} ({status})'
646 texLabel = texLabelTemplate.format(tex=tex, status=texStatus)
648 ax1.axhline(tex.quantity.value, 0, 1, linewidth=2, color='black',
649 label=texLabel)
651 titleTemplate = """
652 {metric} Residual PSF Ellipticity Correlation
653 {bin_range_operator:s} {D.value:.1f}{D.unit:latex}
654 """
655 title = titleTemplate.format(metric=tex.datum.label,
656 bin_range_operator=bin_range_operator,
657 D=D)
658 ax1.set_title(title)
659 ax1.set_xlim(0.0, 20.0)
660 ax1.set_xlabel(
661 '{radius.label} ({unit})'.format(
662 radius=tex.extras['radius'], unit=radius.unit._repr_latex_()))
663 ax1.set_ylabel('Correlation')
665 ax1.legend(loc='upper right', fontsize=16)
667 ext = 'png'
668 pathFormat = '{metric}_D_{D:d}_{Dunits}.{ext}'
669 plotPath = makeFilename(outputPrefix,
670 pathFormat,
671 metric=tex.datum.label,
672 D=int(D.value),
673 Dunits=D.unit,
674 ext=ext)
676 plt.tight_layout() # fix padding
677 plt.savefig(plotPath, dpi=300, ext=ext)
678 plt.close(fig)
679 print("Wrote plot:", plotPath)