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

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""" 

23 

24__all__ = ['plotOutlinedAxline', 

25 'plotAstrometryErrorModel', 

26 'plotAstromErrModelFit', 'plotPhotErrModelFit', 

27 'plotPhotometryErrorModel', 'plotPA1', 'plotAMx'] 

28 

29 

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 

37 

38 

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 

45 

46color = {'all': 'grey', 'bright': 'blue', 

47 'iqr': 'green', 'rms': 'red'} 

48 

49 

50def makeFilename(prefix, formatStr, **kwargs): 

51 """Return a filename for writing to. 

52 

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) 

61 

62 

63def plotOutlinedAxline(axMethod, x, **kwargs): 

64 """Plot an axis line with a white shadow for better contrast. 

65 

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) 

78 

79 if 'linewidth' not in foregroundArgs: 

80 foregroundArgs['linewidth'] = 3 

81 

82 if 'linewidth' in shadowArgs: 

83 shadowArgs['linewidth'] += 1 

84 else: 

85 shadowArgs['linewidth'] = 4 

86 shadowArgs['color'] = 'w' 

87 shadowArgs['label'] = None 

88 

89 axMethod(x, **shadowArgs) 

90 axMethod(x, **foregroundArgs) 

91 

92 

93def plotAstrometryErrorModel(dataset, astromModel, outputPrefix=''): 

94 """Plot angular distance between matched sources from different exposures. 

95 

96 Creates a file containing the plot with a filename beginning with 

97 `outputPrefix`. 

98 

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) 

111 

112 dist = dataset['dist'].quantity 

113 numMatched = len(dist) 

114 dist_median = np.median(dist) 

115 bright_dist_median = np.median(dist[bright]) 

116 

117 fig, ax = plt.subplots(ncols=2, nrows=1, figsize=(18, 12)) 

118 

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') 

124 

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') 

140 

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') 

157 

158 w, = np.where(dist < 200 * u.marcsec) 

159 plotAstromErrModelFit(snr[w], dist[w], astromModel, 

160 ax=ax[1]) 

161 

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']) 

173 

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) 

183 

184 

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 

189 

190 Astrometric Errors 

191 error = C * theta / SNR 

192 

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() 

207 

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') 

216 

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) 

229 

230 

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) 

234 

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 """ 

248 

249 if ax is None: 

250 ax = plt.figure() 

251 xlim = [10, 30] 

252 else: 

253 xlim = ax.get_xlim() 

254 

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') 

264 

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') 

274 

275 

276def plotPhotometryErrorModel(dataset, photomModel, 

277 filterName='', outputPrefix=''): 

278 """Plot photometric RMS for matched sources. 

279 

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) 

294 

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] 

302 

303 mmagrms_median = np.median(mmagRms) 

304 bright_mmagrms_median = np.median(mmagRmsHighSnr) 

305 

306 fig, ax = plt.subplots(ncols=2, nrows=2, figsize=(18, 16)) 

307 

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)) 

328 

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') 

363 

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)) 

378 

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') 

390 

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) 

408 

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') 

414 

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) 

423 

424 

425def plotPA1(pa1, outputPrefix=""): 

426 """Plot the results of calculating the LSST SRC requirement PA1. 

427 

428 Creates a file containing the plot with a filename beginning with 

429 `outputPrefix`. 

430 

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 

446 

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) 

457 

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") 

463 

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) 

478 

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) 

486 

487 

488def plotAMx(job, amx, afx, filterName, amxSpecName='design', outputPrefix=""): 

489 """Plot a histogram of the RMS in relative distance between pairs of 

490 stars. 

491 

492 Creates a file containing the plot with a filename beginning with 

493 `outputPrefix`. 

494 

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 

514 

515 fig = plt.figure(figsize=(10, 6)) 

516 ax1 = fig.add_subplot(1, 1, 1) 

517 

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) 

538 

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) 

547 

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) 

562 

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') 

572 

573 ax1.legend(loc='upper right', fontsize=16) 

574 

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) 

586 

587 plt.tight_layout() # fix padding 

588 plt.savefig(plotPath, dpi=300, format=ext) 

589 plt.close(fig) 

590 print("Wrote plot:", plotPath) 

591 

592 

593def plotTEx(job, tex, filterName, texSpecName='design', outputPrefix=''): 

594 """Plot TEx correlation function measurements and thresholds. 

595 

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. 

610 

611 Effects 

612 ------- 

613 Saves an output plot file to that starts with specified outputPrefix. 

614 

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 

624 

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) 

629 

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) 

639 

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) 

647 

648 ax1.axhline(tex.quantity.value, 0, 1, linewidth=2, color='black', 

649 label=texLabel) 

650 

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') 

664 

665 ax1.legend(loc='upper right', fontsize=16) 

666 

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) 

675 

676 plt.tight_layout() # fix padding 

677 plt.savefig(plotPath, dpi=300, ext=ext) 

678 plt.close(fig) 

679 print("Wrote plot:", plotPath)