369 image_ap_corr_map=None,
370 sources_is_astropy=False,
372 """Compute all summary-statistic fields that depend on the PSF model.
376 summary : `lsst.afw.image.ExposureSummaryStats`
377 Summary object to update in-place.
378 psf : `lsst.afw.detection.Psf` or `None`
379 Point spread function model. If `None`, all fields that depend on
380 the PSF will be reset (generally to NaN).
381 bbox : `lsst.geom.Box2I`
382 Bounding box of the image for which summary stats are being
384 sources : `lsst.afw.table.SourceCatalog` or `astropy.table.Table`
385 Catalog for quantities that are computed from source table columns.
386 If `None`, these quantities will be reset (generally to NaN).
387 The type of this table must correspond to the
388 ``sources_is_astropy`` argument.
389 image_mask : `lsst.afw.image.Mask`, optional
390 Mask image that may be used to compute distance-to-nearest-star
392 sources_is_astropy : `bool`, optional
393 Whether ``sources`` is an `astropy.table.Table` instance instead
394 of an `lsst.afw.table.Catalog` instance. Default is `False` (the
398 summary.psfSigma = nan
402 summary.psfArea = nan
404 summary.psfStarDeltaE1Median = nan
405 summary.psfStarDeltaE2Median = nan
406 summary.psfStarDeltaE1Scatter = nan
407 summary.psfStarDeltaE2Scatter = nan
408 summary.psfStarDeltaSizeMedian = nan
409 summary.psfStarDeltaSizeScatter = nan
410 summary.psfStarScaledDeltaSizeScatter = nan
411 summary.maxDistToNearestPsf = nan
412 summary.psfTraceRadiusDelta = nan
413 summary.psfApFluxDelta = nan
414 summary.psfApCorrSigmaScaledDelta = nan
415 summary.starEMedian = nan
416 summary.starUnNormalizedEMedian = nan
417 summary.starComa1Median = nan
418 summary.starComa2Median = nan
419 summary.starTrefoil1Median = nan
420 summary.starTrefoil2Median = nan
421 summary.starKurtosisMedian = nan
422 summary.starE41Median = nan
423 summary.starE42Median = nan
427 shape = psf.computeShape(bbox.getCenter())
428 summary.psfSigma = shape.getDeterminantRadius()
429 summary.psfIxx = shape.getIxx()
430 summary.psfIyy = shape.getIyy()
431 summary.psfIxy = shape.getIxy()
432 im = psf.computeKernelImage(bbox.getCenter())
435 summary.psfArea = float(np.sum(im.array)**2./np.sum(im.array**2.))
437 if image_mask
is not None:
438 psfApRadius = max(self.config.minPsfApRadiusPix, 3.0*summary.psfSigma)
439 self.log.debug(
"Using radius of %.3f (pixels) for psfApFluxDelta metric", psfApRadius)
443 sampling=self.config.psfGridSampling,
444 ap_radius_pix=psfApRadius,
445 bad_mask_bits=self.config.psfBadMaskPlanes
447 summary.psfTraceRadiusDelta = float(psfTraceRadiusDelta)
448 summary.psfApFluxDelta = float(psfApFluxDelta)
449 if image_ap_corr_map
is not None:
450 if self.config.psfApCorrFieldName
not in image_ap_corr_map.keys():
451 self.log.warning(f
"{self.config.psfApCorrFieldName} not found in "
452 "image_ap_corr_map. Setting psfApCorrSigmaScaledDelta to NaN.")
453 psfApCorrSigmaScaledDelta = nan
455 image_ap_corr_field = image_ap_corr_map[self.config.psfApCorrFieldName]
460 sampling=self.config.psfGridSampling,
461 bad_mask_bits=self.config.psfBadMaskPlanes,
463 summary.psfApCorrSigmaScaledDelta = float(psfApCorrSigmaScaledDelta)
472 nPsfStar = sources[self.config.starSelection].sum()
473 summary.nPsfStar = int(nPsfStar)
475 psf_mask = self.starSelector.run(sources).selected
476 nPsfStarsUsedInStats = psf_mask.sum()
478 if nPsfStarsUsedInStats == 0:
483 if sources_is_astropy:
484 psf_cat = sources[psf_mask]
486 psf_cat = sources[psf_mask].copy(deep=
True)
488 starXX = psf_cat[self.config.starShape +
"_xx"]
489 starYY = psf_cat[self.config.starShape +
"_yy"]
490 starXY = psf_cat[self.config.starShape +
"_xy"]
491 psfXX = psf_cat[self.config.psfShape +
"_xx"]
492 psfYY = psf_cat[self.config.psfShape +
"_yy"]
493 psfXY = psf_cat[self.config.psfShape +
"_xy"]
496 starSize = np.sqrt(starXX/2. + starYY/2.)
498 starE1 = (starXX - starYY)/(starXX + starYY)
499 starE2 = 2*starXY/(starXX + starYY)
500 starSizeMedian = np.median(starSize)
502 starE = np.sqrt(starE1**2.0 + starE2**2.0)
503 starUnNormalizedE = np.sqrt((starXX - starYY)**2.0 + (2.0*starXY)**2.0)
504 starEMedian = np.median(starE)
505 starUnNormalizedEMedian = np.median(starUnNormalizedE)
506 summary.starEMedian = float(starEMedian)
507 summary.starUnNormalizedEMedian = float(starUnNormalizedEMedian)
510 psfSize = np.sqrt(psfXX/2. + psfYY/2.)
511 psfE1 = (psfXX - psfYY)/(psfXX + psfYY)
512 psfE2 = 2*psfXY/(psfXX + psfYY)
514 psfStarDeltaE1Median = np.median(starE1 - psfE1)
515 psfStarDeltaE1Scatter = sigmaMad(starE1 - psfE1, scale=
"normal")
516 psfStarDeltaE2Median = np.median(starE2 - psfE2)
517 psfStarDeltaE2Scatter = sigmaMad(starE2 - psfE2, scale=
"normal")
519 psfStarDeltaSizeMedian = np.median(starSize - psfSize)
520 psfStarDeltaSizeScatter = sigmaMad(starSize - psfSize, scale=
"normal")
521 psfStarScaledDeltaSizeScatter = psfStarDeltaSizeScatter/starSizeMedian
523 summary.psfStarDeltaE1Median = float(psfStarDeltaE1Median)
524 summary.psfStarDeltaE2Median = float(psfStarDeltaE2Median)
525 summary.psfStarDeltaE1Scatter = float(psfStarDeltaE1Scatter)
526 summary.psfStarDeltaE2Scatter = float(psfStarDeltaE2Scatter)
527 summary.psfStarDeltaSizeMedian = float(psfStarDeltaSizeMedian)
528 summary.psfStarDeltaSizeScatter = float(psfStarDeltaSizeScatter)
529 summary.psfStarScaledDeltaSizeScatter = float(psfStarScaledDeltaSizeScatter)
532 if sources_is_astropy:
533 columnNames = psf_cat.colnames
535 columnNames = psf_cat.schema.getNames()
536 if self.config.starHigherOrderMomentBase +
"_03" in columnNames:
537 starM03 = psf_cat[self.config.starHigherOrderMomentBase +
"_03"]
538 starM12 = psf_cat[self.config.starHigherOrderMomentBase +
"_12"]
539 starM21 = psf_cat[self.config.starHigherOrderMomentBase +
"_21"]
540 starM30 = psf_cat[self.config.starHigherOrderMomentBase +
"_30"]
541 starM04 = psf_cat[self.config.starHigherOrderMomentBase +
"_04"]
542 starM13 = psf_cat[self.config.starHigherOrderMomentBase +
"_13"]
543 starM22 = psf_cat[self.config.starHigherOrderMomentBase +
"_22"]
544 starM31 = psf_cat[self.config.starHigherOrderMomentBase +
"_31"]
545 starM40 = psf_cat[self.config.starHigherOrderMomentBase +
"_40"]
547 starComa1Median = np.nanmedian(starM30 + starM12)
548 starComa2Median = np.nanmedian(starM21 + starM03)
549 starTrefoil1Median = np.nanmedian(starM30 - 3.0*starM12)
550 starTrefoil2Median = np.nanmedian(3.0*starM21 - starM03)
551 starKurtosisMedian = np.nanmedian(starM40 + 2.0*starM22 + starM04)
552 starE41Median = np.nanmedian(starM40 - starM04)
553 starE42Median = np.nanmedian(2.0*(starM31 + starM13))
555 summary.starComa1Median = float(starComa1Median)
556 summary.starComa2Median = float(starComa2Median)
557 summary.starTrefoil1Median = float(starTrefoil1Median)
558 summary.starTrefoil2Median = float(starTrefoil2Median)
559 summary.starKurtosisMedian = float(starKurtosisMedian)
560 summary.starE41Median = float(starE41Median)
561 summary.starE42Median = float(starE42Median)
563 self.log.warning(
"Higher-order moments with base column name %s not found in source "
564 "catalog. Setting the derived metrics (i.e. coma1[2], trefoil1[2], "
565 "kurtosis, e4_1, and e4_2) to nan.", self.config.starHigherOrderMomentBase)
567 if image_mask
is not None:
571 sampling=self.config.psfSampling,
572 bad_mask_bits=self.config.psfBadMaskPlanes
574 summary.maxDistToNearestPsf = float(maxDistToNearestPsf)
577 """Compute all summary-statistic fields that depend on the WCS model.
581 summary : `lsst.afw.image.ExposureSummaryStats`
582 Summary object to update in-place.
583 wcs : `lsst.afw.geom.SkyWcs` or `None`
584 Astrometric calibration model. If `None`, all fields that depend
585 on the WCS will be reset (generally to NaN).
586 bbox : `lsst.geom.Box2I`
587 Bounding box of the image for which summary stats are being
589 visitInfo : `lsst.afw.image.VisitInfo`
590 Observation information used in together with ``wcs`` to compute
594 summary.raCorners = [nan]*4
595 summary.decCorners = [nan]*4
598 summary.pixelScale = nan
599 summary.zenithDistance = nan
604 sph_pts = wcs.pixelToSky(
geom.Box2D(bbox).getCorners())
605 summary.raCorners = [float(sph.getRa().asDegrees())
for sph
in sph_pts]
606 summary.decCorners = [float(sph.getDec().asDegrees())
for sph
in sph_pts]
608 sph_pt = wcs.pixelToSky(bbox.getCenter())
609 summary.ra = sph_pt.getRa().asDegrees()
610 summary.dec = sph_pt.getDec().asDegrees()
611 summary.pixelScale = wcs.getPixelScale(bbox.getCenter()).asArcseconds()
613 date = visitInfo.getDate()
620 observatory = visitInfo.getObservatory()
621 loc = EarthLocation(lat=observatory.getLatitude().asDegrees()*units.deg,
622 lon=observatory.getLongitude().asDegrees()*units.deg,
623 height=observatory.getElevation()*units.m)
624 obstime = Time(visitInfo.getDate().get(system=DateTime.MJD),
625 location=loc, format=
"mjd")
627 summary.ra*units.degree,
628 summary.dec*units.degree,
632 with warnings.catch_warnings():
633 warnings.simplefilter(
"ignore")
634 altaz = coord.transform_to(AltAz)
636 summary.zenithDistance = float(90.0 - altaz.alt.degree)
714 """Compute effective exposure time statistics to estimate depth.
716 The effective exposure time is the equivalent shutter open
717 time that would be needed under nominal conditions to give the
718 same signal-to-noise for a point source as what is achieved by
719 the observation of interest. This metric combines measurements
720 of the point-spread function, the sky brightness, and the
721 transparency. It assumes that the observation is
722 sky-background dominated.
724 .. _teff_definitions:
726 The effective exposure time and its subcomponents are defined in [1]_.
731 .. [1] Neilsen, E.H., Bernstein, G., Gruendl, R., and Kent, S. (2016).
732 Limiting Magnitude, \tau, teff, and Image Quality in DES Year 1
733 https://www.osti.gov/biblio/1250877/
737 summary : `lsst.afw.image.ExposureSummaryStats`
738 Summary object to update in-place.
739 exposure : `lsst.afw.image.ExposureF`
740 Exposure to grab band and exposure time metadata
744 summary.effTime = nan
745 summary.effTimePsfSigmaScale = nan
746 summary.effTimeSkyBgScale = nan
747 summary.effTimeZeroPointScale = nan
749 exposureTime = exposure.getInfo().getVisitInfo().getExposureTime()
750 filterLabel = exposure.getFilter()
751 if (filterLabel
is None)
or (
not filterLabel.hasBandLabel):
754 band = filterLabel.bandLabel
757 self.log.warning(
"No band associated with exposure; effTime not calculated.")
761 if np.isnan(summary.psfSigma):
762 self.log.debug(
"PSF sigma is NaN")
764 elif band
not in self.config.fiducialPsfSigma:
765 self.log.debug(f
"Fiducial PSF value not found for {band}")
768 fiducialPsfSigma = self.config.fiducialPsfSigma[band]
769 f_eff = (summary.psfSigma / fiducialPsfSigma)**-2
773 if np.isnan(summary.zeroPoint):
774 self.log.debug(
"Zero point is NaN")
776 elif band
not in self.config.fiducialZeroPoint:
777 self.log.debug(f
"Fiducial zero point value not found for {band}")
780 fiducialZeroPoint = self.config.fiducialZeroPoint[band]
781 zeroPointDiff = fiducialZeroPoint - (summary.zeroPoint - 2.5*np.log10(exposureTime))
782 c_eff = min(10**(-2.0*(zeroPointDiff)/2.5), self.config.maxEffectiveTransparency)
785 if np.isnan(summary.skyBg):
786 self.log.debug(
"Sky background is NaN")
788 elif band
not in self.config.fiducialSkyBackground:
789 self.log.debug(f
"Fiducial sky background value not found for {band}")
792 fiducialSkyBackground = self.config.fiducialSkyBackground[band]
793 b_eff = fiducialSkyBackground/(summary.skyBg/exposureTime)
799 if band
not in self.config.fiducialMagLim:
800 self.log.debug(f
"Fiducial magnitude limit not found for {band}")
802 elif band
not in self.config.fiducialExpTime:
803 self.log.debug(f
"Fiducial exposure time not found for {band}")
807 self.config.fiducialMagLim[band],
808 self.config.fiducialExpTime[band])
811 summary.effTime = float(effectiveTime)
812 summary.effTimePsfSigmaScale = float(f_eff)
813 summary.effTimeSkyBgScale = float(b_eff)
814 summary.effTimeZeroPointScale = float(c_eff)