284 ):
285 """Compute all summary-statistic fields that depend on the PSF model.
286
287 Parameters
288 ----------
289 summary : `lsst.afw.image.ExposureSummaryStats`
290 Summary object to update in-place.
291 psf : `lsst.afw.detection.Psf` or `None`
292 Point spread function model. If `None`, all fields that depend on
293 the PSF will be reset (generally to NaN).
294 bbox : `lsst.geom.Box2I`
295 Bounding box of the image for which summary stats are being
296 computed.
297 sources : `lsst.afw.table.SourceCatalog` or `astropy.table.Table`
298 Catalog for quantities that are computed from source table columns.
299 If `None`, these quantities will be reset (generally to NaN).
300 The type of this table must correspond to the
301 ``sources_is_astropy`` argument.
302 image_mask : `lsst.afw.image.Mask`, optional
303 Mask image that may be used to compute distance-to-nearest-star
304 metrics.
305 sources_is_astropy : `bool`, optional
306 Whether ``sources`` is an `astropy.table.Table` instance instead
307 of an `lsst.afw.table.Catalog` instance. Default is `False` (the
308 latter).
309 """
310 nan = float("nan")
311 summary.psfSigma = nan
312 summary.psfIxx = nan
313 summary.psfIyy = nan
314 summary.psfIxy = nan
315 summary.psfArea = nan
316 summary.nPsfStar = 0
317 summary.psfStarDeltaE1Median = nan
318 summary.psfStarDeltaE2Median = nan
319 summary.psfStarDeltaE1Scatter = nan
320 summary.psfStarDeltaE2Scatter = nan
321 summary.psfStarDeltaSizeMedian = nan
322 summary.psfStarDeltaSizeScatter = nan
323 summary.psfStarScaledDeltaSizeScatter = nan
324 summary.maxDistToNearestPsf = nan
325 summary.psfTraceRadiusDelta = nan
326 summary.psfApFluxDelta = nan
327 summary.psfApCorrSigmaScaledDelta = nan
328
329 if psf is None:
330 return
331 shape = psf.computeShape(bbox.getCenter())
332 summary.psfSigma = shape.getDeterminantRadius()
333 summary.psfIxx = shape.getIxx()
334 summary.psfIyy = shape.getIyy()
335 summary.psfIxy = shape.getIxy()
336 im = psf.computeKernelImage(bbox.getCenter())
337
338
339
340
341 summary.psfArea = float(np.sum(im.array)/np.sum(im.array**2.))
342
343 if image_mask is not None:
344 psfApRadius = max(self.config.minPsfApRadiusPix, 3.0*summary.psfSigma)
345 self.log.debug("Using radius of %.3f (pixels) for psfApFluxDelta metric", psfApRadius)
346 psfTraceRadiusDelta, psfApFluxDelta = compute_psf_image_deltas(
347 image_mask,
348 psf,
349 sampling=self.config.psfGridSampling,
350 ap_radius_pix=psfApRadius,
351 bad_mask_bits=self.config.psfBadMaskPlanes
352 )
353 summary.psfTraceRadiusDelta = float(psfTraceRadiusDelta)
354 summary.psfApFluxDelta = float(psfApFluxDelta)
355 if image_ap_corr_map is not None:
356 if self.config.psfApCorrFieldName not in image_ap_corr_map.keys():
357 self.log.warning(f"{self.config.psfApCorrFieldName} not found in "
358 "image_ap_corr_map. Setting psfApCorrSigmaScaledDelta to NaN.")
359 psfApCorrSigmaScaledDelta = nan
360 else:
361 image_ap_corr_field = image_ap_corr_map[self.config.psfApCorrFieldName]
362 psfApCorrSigmaScaledDelta = compute_ap_corr_sigma_scaled_delta(
363 image_mask,
364 image_ap_corr_field,
365 summary.psfSigma,
366 sampling=self.config.psfGridSampling,
367 bad_mask_bits=self.config.psfBadMaskPlanes,
368 )
369 summary.psfApCorrSigmaScaledDelta = float(psfApCorrSigmaScaledDelta)
370
371 if sources is None:
372
373
374
375 return
376
377
378 nPsfStar = sources[self.config.starSelection].sum()
379 summary.nPsfStar = int(nPsfStar)
380
381 psf_mask = self.starSelector.run(sources).selected
382 nPsfStarsUsedInStats = psf_mask.sum()
383
384 if nPsfStarsUsedInStats == 0:
385
386
387 return
388
389 if sources_is_astropy:
390 psf_cat = sources[psf_mask]
391 else:
392 psf_cat = sources[psf_mask].copy(deep=True)
393
394 starXX = psf_cat[self.config.starShape + '_xx']
395 starYY = psf_cat[self.config.starShape + '_yy']
396 starXY = psf_cat[self.config.starShape + '_xy']
397 psfXX = psf_cat[self.config.psfShape + '_xx']
398 psfYY = psf_cat[self.config.psfShape + '_yy']
399 psfXY = psf_cat[self.config.psfShape + '_xy']
400
401
402 starSize = np.sqrt(starXX/2. + starYY/2.)
403
404 starE1 = (starXX - starYY)/(starXX + starYY)
405 starE2 = 2*starXY/(starXX + starYY)
406 starSizeMedian = np.median(starSize)
407
408
409 psfSize = np.sqrt(psfXX/2. + psfYY/2.)
410 psfE1 = (psfXX - psfYY)/(psfXX + psfYY)
411 psfE2 = 2*psfXY/(psfXX + psfYY)
412
413 psfStarDeltaE1Median = np.median(starE1 - psfE1)
414 psfStarDeltaE1Scatter = sigmaMad(starE1 - psfE1, scale='normal')
415 psfStarDeltaE2Median = np.median(starE2 - psfE2)
416 psfStarDeltaE2Scatter = sigmaMad(starE2 - psfE2, scale='normal')
417
418 psfStarDeltaSizeMedian = np.median(starSize - psfSize)
419 psfStarDeltaSizeScatter = sigmaMad(starSize - psfSize, scale='normal')
420 psfStarScaledDeltaSizeScatter = psfStarDeltaSizeScatter/starSizeMedian
421
422 summary.psfStarDeltaE1Median = float(psfStarDeltaE1Median)
423 summary.psfStarDeltaE2Median = float(psfStarDeltaE2Median)
424 summary.psfStarDeltaE1Scatter = float(psfStarDeltaE1Scatter)
425 summary.psfStarDeltaE2Scatter = float(psfStarDeltaE2Scatter)
426 summary.psfStarDeltaSizeMedian = float(psfStarDeltaSizeMedian)
427 summary.psfStarDeltaSizeScatter = float(psfStarDeltaSizeScatter)
428 summary.psfStarScaledDeltaSizeScatter = float(psfStarScaledDeltaSizeScatter)
429
430 if image_mask is not None:
431 maxDistToNearestPsf = maximum_nearest_psf_distance(
432 image_mask,
433 psf_cat,
434 sampling=self.config.psfSampling,
435 bad_mask_bits=self.config.psfBadMaskPlanes
436 )
437 summary.maxDistToNearestPsf = float(maxDistToNearestPsf)
438