359 image_ap_corr_map=None,
360 sources_is_astropy=False,
362 """Compute all summary-statistic fields that depend on the PSF model.
366 summary : `lsst.afw.image.ExposureSummaryStats`
367 Summary object to update in-place.
368 psf : `lsst.afw.detection.Psf` or `None`
369 Point spread function model. If `None`, all fields that depend on
370 the PSF will be reset (generally to NaN).
371 bbox : `lsst.geom.Box2I`
372 Bounding box of the image for which summary stats are being
374 sources : `lsst.afw.table.SourceCatalog` or `astropy.table.Table`
375 Catalog for quantities that are computed from source table columns.
376 If `None`, these quantities will be reset (generally to NaN).
377 The type of this table must correspond to the
378 ``sources_is_astropy`` argument.
379 image_mask : `lsst.afw.image.Mask`, optional
380 Mask image that may be used to compute distance-to-nearest-star
382 sources_is_astropy : `bool`, optional
383 Whether ``sources`` is an `astropy.table.Table` instance instead
384 of an `lsst.afw.table.Catalog` instance. Default is `False` (the
388 summary.psfSigma = nan
392 summary.psfArea = nan
394 summary.psfStarDeltaE1Median = nan
395 summary.psfStarDeltaE2Median = nan
396 summary.psfStarDeltaE1Scatter = nan
397 summary.psfStarDeltaE2Scatter = nan
398 summary.psfStarDeltaSizeMedian = nan
399 summary.psfStarDeltaSizeScatter = nan
400 summary.psfStarScaledDeltaSizeScatter = nan
401 summary.maxDistToNearestPsf = nan
402 summary.psfTraceRadiusDelta = nan
403 summary.psfApFluxDelta = nan
404 summary.psfApCorrSigmaScaledDelta = nan
405 summary.starEMedian = nan
406 summary.starUnNormalizedEMedian = nan
407 summary.starComa1Median = nan
408 summary.starComa2Median = nan
409 summary.starTrefoil1Median = nan
410 summary.starTrefoil2Median = nan
411 summary.starKurtosisMedian = nan
412 summary.starE41Median = nan
413 summary.starE42Median = nan
417 shape = psf.computeShape(bbox.getCenter())
418 summary.psfSigma = shape.getDeterminantRadius()
419 summary.psfIxx = shape.getIxx()
420 summary.psfIyy = shape.getIyy()
421 summary.psfIxy = shape.getIxy()
422 im = psf.computeKernelImage(bbox.getCenter())
425 summary.psfArea = float(np.sum(im.array)**2./np.sum(im.array**2.))
427 if image_mask
is not None:
428 psfApRadius = max(self.config.minPsfApRadiusPix, 3.0*summary.psfSigma)
429 self.log.debug(
"Using radius of %.3f (pixels) for psfApFluxDelta metric", psfApRadius)
433 sampling=self.config.psfGridSampling,
434 ap_radius_pix=psfApRadius,
435 bad_mask_bits=self.config.psfBadMaskPlanes
437 summary.psfTraceRadiusDelta = float(psfTraceRadiusDelta)
438 summary.psfApFluxDelta = float(psfApFluxDelta)
439 if image_ap_corr_map
is not None:
440 if self.config.psfApCorrFieldName
not in image_ap_corr_map.keys():
441 self.log.warning(f
"{self.config.psfApCorrFieldName} not found in "
442 "image_ap_corr_map. Setting psfApCorrSigmaScaledDelta to NaN.")
443 psfApCorrSigmaScaledDelta = nan
445 image_ap_corr_field = image_ap_corr_map[self.config.psfApCorrFieldName]
450 sampling=self.config.psfGridSampling,
451 bad_mask_bits=self.config.psfBadMaskPlanes,
453 summary.psfApCorrSigmaScaledDelta = float(psfApCorrSigmaScaledDelta)
462 nPsfStar = sources[self.config.starSelection].sum()
463 summary.nPsfStar = int(nPsfStar)
465 psf_mask = self.starSelector.run(sources).selected
466 nPsfStarsUsedInStats = psf_mask.sum()
468 if nPsfStarsUsedInStats == 0:
473 if sources_is_astropy:
474 psf_cat = sources[psf_mask]
476 psf_cat = sources[psf_mask].copy(deep=
True)
478 starXX = psf_cat[self.config.starShape +
"_xx"]
479 starYY = psf_cat[self.config.starShape +
"_yy"]
480 starXY = psf_cat[self.config.starShape +
"_xy"]
481 psfXX = psf_cat[self.config.psfShape +
"_xx"]
482 psfYY = psf_cat[self.config.psfShape +
"_yy"]
483 psfXY = psf_cat[self.config.psfShape +
"_xy"]
486 starSize = np.sqrt(starXX/2. + starYY/2.)
488 starE1 = (starXX - starYY)/(starXX + starYY)
489 starE2 = 2*starXY/(starXX + starYY)
490 starSizeMedian = np.median(starSize)
492 starE = np.sqrt(starE1**2.0 + starE2**2.0)
493 starUnNormalizedE = np.sqrt((starXX - starYY)**2.0 + (2.0*starXY)**2.0)
494 starEMedian = np.median(starE)
495 starUnNormalizedEMedian = np.median(starUnNormalizedE)
496 summary.starEMedian = float(starEMedian)
497 summary.starUnNormalizedEMedian = float(starUnNormalizedEMedian)
500 psfSize = np.sqrt(psfXX/2. + psfYY/2.)
501 psfE1 = (psfXX - psfYY)/(psfXX + psfYY)
502 psfE2 = 2*psfXY/(psfXX + psfYY)
504 psfStarDeltaE1Median = np.median(starE1 - psfE1)
505 psfStarDeltaE1Scatter = sigmaMad(starE1 - psfE1, scale=
"normal")
506 psfStarDeltaE2Median = np.median(starE2 - psfE2)
507 psfStarDeltaE2Scatter = sigmaMad(starE2 - psfE2, scale=
"normal")
509 psfStarDeltaSizeMedian = np.median(starSize - psfSize)
510 psfStarDeltaSizeScatter = sigmaMad(starSize - psfSize, scale=
"normal")
511 psfStarScaledDeltaSizeScatter = psfStarDeltaSizeScatter/starSizeMedian
513 summary.psfStarDeltaE1Median = float(psfStarDeltaE1Median)
514 summary.psfStarDeltaE2Median = float(psfStarDeltaE2Median)
515 summary.psfStarDeltaE1Scatter = float(psfStarDeltaE1Scatter)
516 summary.psfStarDeltaE2Scatter = float(psfStarDeltaE2Scatter)
517 summary.psfStarDeltaSizeMedian = float(psfStarDeltaSizeMedian)
518 summary.psfStarDeltaSizeScatter = float(psfStarDeltaSizeScatter)
519 summary.psfStarScaledDeltaSizeScatter = float(psfStarScaledDeltaSizeScatter)
522 if sources_is_astropy:
523 columnNames = psf_cat.colnames
525 columnNames = psf_cat.schema.getNames()
526 if self.config.starHigherOrderMomentBase +
"_03" in columnNames:
527 starM03 = psf_cat[self.config.starHigherOrderMomentBase +
"_03"]
528 starM12 = psf_cat[self.config.starHigherOrderMomentBase +
"_12"]
529 starM21 = psf_cat[self.config.starHigherOrderMomentBase +
"_21"]
530 starM30 = psf_cat[self.config.starHigherOrderMomentBase +
"_30"]
531 starM04 = psf_cat[self.config.starHigherOrderMomentBase +
"_04"]
532 starM13 = psf_cat[self.config.starHigherOrderMomentBase +
"_13"]
533 starM22 = psf_cat[self.config.starHigherOrderMomentBase +
"_22"]
534 starM31 = psf_cat[self.config.starHigherOrderMomentBase +
"_31"]
535 starM40 = psf_cat[self.config.starHigherOrderMomentBase +
"_40"]
537 starComa1Median = np.nanmedian(starM30 + starM12)
538 starComa2Median = np.nanmedian(starM21 + starM03)
539 starTrefoil1Median = np.nanmedian(starM30 - 3.0*starM12)
540 starTrefoil2Median = np.nanmedian(3.0*starM21 - starM03)
541 starKurtosisMedian = np.nanmedian(starM40 + 2.0*starM22 + starM04)
542 starE41Median = np.nanmedian(starM40 - starM04)
543 starE42Median = np.nanmedian(2.0*(starM31 + starM13))
545 summary.starComa1Median = float(starComa1Median)
546 summary.starComa2Median = float(starComa2Median)
547 summary.starTrefoil1Median = float(starTrefoil1Median)
548 summary.starTrefoil2Median = float(starTrefoil2Median)
549 summary.starKurtosisMedian = float(starKurtosisMedian)
550 summary.starE41Median = float(starE41Median)
551 summary.starE42Median = float(starE42Median)
553 self.log.warning(
"Higher-order moments with base column name %s not found in source "
554 "catalog. Setting the derived metrics (i.e. coma1[2], trefoil1[2], "
555 "kurtosis, e4_1, and e4_2) to nan.", self.config.starHigherOrderMomentBase)
557 if image_mask
is not None:
561 sampling=self.config.psfSampling,
562 bad_mask_bits=self.config.psfBadMaskPlanes
564 summary.maxDistToNearestPsf = float(maxDistToNearestPsf)
567 """Compute all summary-statistic fields that depend on the WCS model.
571 summary : `lsst.afw.image.ExposureSummaryStats`
572 Summary object to update in-place.
573 wcs : `lsst.afw.geom.SkyWcs` or `None`
574 Astrometric calibration model. If `None`, all fields that depend
575 on the WCS will be reset (generally to NaN).
576 bbox : `lsst.geom.Box2I`
577 Bounding box of the image for which summary stats are being
579 visitInfo : `lsst.afw.image.VisitInfo`
580 Observation information used in together with ``wcs`` to compute
584 summary.raCorners = [nan]*4
585 summary.decCorners = [nan]*4
588 summary.pixelScale = nan
589 summary.zenithDistance = nan
594 sph_pts = wcs.pixelToSky(
geom.Box2D(bbox).getCorners())
595 summary.raCorners = [float(sph.getRa().asDegrees())
for sph
in sph_pts]
596 summary.decCorners = [float(sph.getDec().asDegrees())
for sph
in sph_pts]
598 sph_pt = wcs.pixelToSky(bbox.getCenter())
599 summary.ra = sph_pt.getRa().asDegrees()
600 summary.dec = sph_pt.getDec().asDegrees()
601 summary.pixelScale = wcs.getPixelScale(bbox.getCenter()).asArcseconds()
603 date = visitInfo.getDate()
610 observatory = visitInfo.getObservatory()
611 loc = EarthLocation(lat=observatory.getLatitude().asDegrees()*units.deg,
612 lon=observatory.getLongitude().asDegrees()*units.deg,
613 height=observatory.getElevation()*units.m)
614 obstime = Time(visitInfo.getDate().get(system=DateTime.MJD),
615 location=loc, format=
"mjd")
617 summary.ra*units.degree,
618 summary.dec*units.degree,
622 with warnings.catch_warnings():
623 warnings.simplefilter(
"ignore")
624 altaz = coord.transform_to(AltAz)
626 summary.zenithDistance = float(90.0 - altaz.alt.degree)
704 """Compute effective exposure time statistics to estimate depth.
706 The effective exposure time is the equivalent shutter open
707 time that would be needed under nominal conditions to give the
708 same signal-to-noise for a point source as what is achieved by
709 the observation of interest. This metric combines measurements
710 of the point-spread function, the sky brightness, and the
711 transparency. It assumes that the observation is
712 sky-background dominated.
714 .. _teff_definitions:
716 The effective exposure time and its subcomponents are defined in [1]_.
721 .. [1] Neilsen, E.H., Bernstein, G., Gruendl, R., and Kent, S. (2016).
722 Limiting Magnitude, \tau, teff, and Image Quality in DES Year 1
723 https://www.osti.gov/biblio/1250877/
727 summary : `lsst.afw.image.ExposureSummaryStats`
728 Summary object to update in-place.
729 exposure : `lsst.afw.image.ExposureF`
730 Exposure to grab band and exposure time metadata
734 summary.effTime = nan
735 summary.effTimePsfSigmaScale = nan
736 summary.effTimeSkyBgScale = nan
737 summary.effTimeZeroPointScale = nan
739 exposureTime = exposure.getInfo().getVisitInfo().getExposureTime()
740 filterLabel = exposure.getFilter()
741 if (filterLabel
is None)
or (
not filterLabel.hasBandLabel):
744 band = filterLabel.bandLabel
747 self.log.warning(
"No band associated with exposure; effTime not calculated.")
751 if np.isnan(summary.psfSigma):
752 self.log.debug(
"PSF sigma is NaN")
754 elif band
not in self.config.fiducialPsfSigma:
755 self.log.debug(f
"Fiducial PSF value not found for {band}")
758 fiducialPsfSigma = self.config.fiducialPsfSigma[band]
759 f_eff = (summary.psfSigma / fiducialPsfSigma)**-2
763 if np.isnan(summary.zeroPoint):
764 self.log.debug(
"Zero point is NaN")
766 elif band
not in self.config.fiducialZeroPoint:
767 self.log.debug(f
"Fiducial zero point value not found for {band}")
770 fiducialZeroPoint = self.config.fiducialZeroPoint[band]
771 zeroPointDiff = fiducialZeroPoint - (summary.zeroPoint - 2.5*np.log10(exposureTime))
772 c_eff = min(10**(-2.0*(zeroPointDiff)/2.5), self.config.maxEffectiveTransparency)
775 if np.isnan(summary.skyBg):
776 self.log.debug(
"Sky background is NaN")
778 elif band
not in self.config.fiducialSkyBackground:
779 self.log.debug(f
"Fiducial sky background value not found for {band}")
782 fiducialSkyBackground = self.config.fiducialSkyBackground[band]
783 b_eff = fiducialSkyBackground/(summary.skyBg/exposureTime)
789 if band
not in self.config.fiducialMagLim:
790 self.log.debug(f
"Fiducial magnitude limit not found for {band}")
792 elif band
not in self.config.fiducialExpTime:
793 self.log.debug(f
"Fiducial exposure time not found for {band}")
797 self.config.fiducialMagLim[band],
798 self.config.fiducialExpTime[band])
801 summary.effTime = float(effectiveTime)
802 summary.effTimePsfSigmaScale = float(f_eff)
803 summary.effTimeSkyBgScale = float(b_eff)
804 summary.effTimeZeroPointScale = float(c_eff)