367 image_ap_corr_map=None,
368 sources_is_astropy=False,
370 """Compute all summary-statistic fields that depend on the PSF model.
374 summary : `lsst.afw.image.ExposureSummaryStats`
375 Summary object to update in-place.
376 psf : `lsst.afw.detection.Psf` or `None`
377 Point spread function model. If `None`, all fields that depend on
378 the PSF will be reset (generally to NaN).
379 bbox : `lsst.geom.Box2I`
380 Bounding box of the image for which summary stats are being
382 sources : `lsst.afw.table.SourceCatalog` or `astropy.table.Table`
383 Catalog for quantities that are computed from source table columns.
384 If `None`, these quantities will be reset (generally to NaN).
385 The type of this table must correspond to the
386 ``sources_is_astropy`` argument.
387 image_mask : `lsst.afw.image.Mask`, optional
388 Mask image that may be used to compute distance-to-nearest-star
390 sources_is_astropy : `bool`, optional
391 Whether ``sources`` is an `astropy.table.Table` instance instead
392 of an `lsst.afw.table.Catalog` instance. Default is `False` (the
396 summary.psfSigma = nan
400 summary.psfArea = nan
402 summary.psfStarDeltaE1Median = nan
403 summary.psfStarDeltaE2Median = nan
404 summary.psfStarDeltaE1Scatter = nan
405 summary.psfStarDeltaE2Scatter = nan
406 summary.psfStarDeltaSizeMedian = nan
407 summary.psfStarDeltaSizeScatter = nan
408 summary.psfStarScaledDeltaSizeScatter = nan
409 summary.maxDistToNearestPsf = nan
410 summary.psfTraceRadiusDelta = nan
411 summary.psfApFluxDelta = nan
412 summary.psfApCorrSigmaScaledDelta = nan
413 summary.starEMedian = nan
414 summary.starUnNormalizedEMedian = nan
415 summary.starComa1Median = nan
416 summary.starComa2Median = nan
417 summary.starTrefoil1Median = nan
418 summary.starTrefoil2Median = nan
419 summary.starKurtosisMedian = nan
420 summary.starE41Median = nan
421 summary.starE42Median = nan
425 shape = psf.computeShape(bbox.getCenter())
426 summary.psfSigma = shape.getDeterminantRadius()
427 summary.psfIxx = shape.getIxx()
428 summary.psfIyy = shape.getIyy()
429 summary.psfIxy = shape.getIxy()
430 im = psf.computeKernelImage(bbox.getCenter())
433 summary.psfArea = float(np.sum(im.array)**2./np.sum(im.array**2.))
435 if image_mask
is not None:
436 psfApRadius = max(self.config.minPsfApRadiusPix, 3.0*summary.psfSigma)
437 self.log.debug(
"Using radius of %.3f (pixels) for psfApFluxDelta metric", psfApRadius)
441 sampling=self.config.psfGridSampling,
442 ap_radius_pix=psfApRadius,
443 bad_mask_bits=self.config.psfBadMaskPlanes
445 summary.psfTraceRadiusDelta = float(psfTraceRadiusDelta)
446 summary.psfApFluxDelta = float(psfApFluxDelta)
447 if image_ap_corr_map
is not None:
448 if self.config.psfApCorrFieldName
not in image_ap_corr_map.keys():
449 self.log.warning(f
"{self.config.psfApCorrFieldName} not found in "
450 "image_ap_corr_map. Setting psfApCorrSigmaScaledDelta to NaN.")
451 psfApCorrSigmaScaledDelta = nan
453 image_ap_corr_field = image_ap_corr_map[self.config.psfApCorrFieldName]
458 sampling=self.config.psfGridSampling,
459 bad_mask_bits=self.config.psfBadMaskPlanes,
461 summary.psfApCorrSigmaScaledDelta = float(psfApCorrSigmaScaledDelta)
470 nPsfStar = sources[self.config.starSelection].sum()
471 summary.nPsfStar = int(nPsfStar)
473 psf_mask = self.starSelector.run(sources).selected
474 nPsfStarsUsedInStats = psf_mask.sum()
476 if nPsfStarsUsedInStats == 0:
481 if sources_is_astropy:
482 psf_cat = sources[psf_mask]
484 psf_cat = sources[psf_mask].copy(deep=
True)
486 starXX = psf_cat[self.config.starShape +
"_xx"]
487 starYY = psf_cat[self.config.starShape +
"_yy"]
488 starXY = psf_cat[self.config.starShape +
"_xy"]
489 psfXX = psf_cat[self.config.psfShape +
"_xx"]
490 psfYY = psf_cat[self.config.psfShape +
"_yy"]
491 psfXY = psf_cat[self.config.psfShape +
"_xy"]
494 starSize = np.sqrt(starXX/2. + starYY/2.)
496 starE1 = (starXX - starYY)/(starXX + starYY)
497 starE2 = 2*starXY/(starXX + starYY)
498 starSizeMedian = np.median(starSize)
500 starE = np.sqrt(starE1**2.0 + starE2**2.0)
501 starUnNormalizedE = np.sqrt((starXX - starYY)**2.0 + (2.0*starXY)**2.0)
502 starEMedian = np.median(starE)
503 starUnNormalizedEMedian = np.median(starUnNormalizedE)
504 summary.starEMedian = float(starEMedian)
505 summary.starUnNormalizedEMedian = float(starUnNormalizedEMedian)
508 psfSize = np.sqrt(psfXX/2. + psfYY/2.)
509 psfE1 = (psfXX - psfYY)/(psfXX + psfYY)
510 psfE2 = 2*psfXY/(psfXX + psfYY)
512 psfStarDeltaE1Median = np.median(starE1 - psfE1)
513 psfStarDeltaE1Scatter = sigmaMad(starE1 - psfE1, scale=
"normal")
514 psfStarDeltaE2Median = np.median(starE2 - psfE2)
515 psfStarDeltaE2Scatter = sigmaMad(starE2 - psfE2, scale=
"normal")
517 psfStarDeltaSizeMedian = np.median(starSize - psfSize)
518 psfStarDeltaSizeScatter = sigmaMad(starSize - psfSize, scale=
"normal")
519 psfStarScaledDeltaSizeScatter = psfStarDeltaSizeScatter/starSizeMedian
521 summary.psfStarDeltaE1Median = float(psfStarDeltaE1Median)
522 summary.psfStarDeltaE2Median = float(psfStarDeltaE2Median)
523 summary.psfStarDeltaE1Scatter = float(psfStarDeltaE1Scatter)
524 summary.psfStarDeltaE2Scatter = float(psfStarDeltaE2Scatter)
525 summary.psfStarDeltaSizeMedian = float(psfStarDeltaSizeMedian)
526 summary.psfStarDeltaSizeScatter = float(psfStarDeltaSizeScatter)
527 summary.psfStarScaledDeltaSizeScatter = float(psfStarScaledDeltaSizeScatter)
530 if sources_is_astropy:
531 columnNames = psf_cat.colnames
533 columnNames = psf_cat.schema.getNames()
534 if self.config.starHigherOrderMomentBase +
"_03" in columnNames:
535 starM03 = psf_cat[self.config.starHigherOrderMomentBase +
"_03"]
536 starM12 = psf_cat[self.config.starHigherOrderMomentBase +
"_12"]
537 starM21 = psf_cat[self.config.starHigherOrderMomentBase +
"_21"]
538 starM30 = psf_cat[self.config.starHigherOrderMomentBase +
"_30"]
539 starM04 = psf_cat[self.config.starHigherOrderMomentBase +
"_04"]
540 starM13 = psf_cat[self.config.starHigherOrderMomentBase +
"_13"]
541 starM22 = psf_cat[self.config.starHigherOrderMomentBase +
"_22"]
542 starM31 = psf_cat[self.config.starHigherOrderMomentBase +
"_31"]
543 starM40 = psf_cat[self.config.starHigherOrderMomentBase +
"_40"]
545 starComa1Median = np.nanmedian(starM30 + starM12)
546 starComa2Median = np.nanmedian(starM21 + starM03)
547 starTrefoil1Median = np.nanmedian(starM30 - 3.0*starM12)
548 starTrefoil2Median = np.nanmedian(3.0*starM21 - starM03)
549 starKurtosisMedian = np.nanmedian(starM40 + 2.0*starM22 + starM04)
550 starE41Median = np.nanmedian(starM40 - starM04)
551 starE42Median = np.nanmedian(2.0*(starM31 + starM13))
553 summary.starComa1Median = float(starComa1Median)
554 summary.starComa2Median = float(starComa2Median)
555 summary.starTrefoil1Median = float(starTrefoil1Median)
556 summary.starTrefoil2Median = float(starTrefoil2Median)
557 summary.starKurtosisMedian = float(starKurtosisMedian)
558 summary.starE41Median = float(starE41Median)
559 summary.starE42Median = float(starE42Median)
561 self.log.warning(
"Higher-order moments with base column name %s not found in source "
562 "catalog. Setting the derived metrics (i.e. coma1[2], trefoil1[2], "
563 "kurtosis, e4_1, and e4_2) to nan.", self.config.starHigherOrderMomentBase)
565 if image_mask
is not None:
569 sampling=self.config.psfSampling,
570 bad_mask_bits=self.config.psfBadMaskPlanes
572 summary.maxDistToNearestPsf = float(maxDistToNearestPsf)
575 """Compute all summary-statistic fields that depend on the WCS model.
579 summary : `lsst.afw.image.ExposureSummaryStats`
580 Summary object to update in-place.
581 wcs : `lsst.afw.geom.SkyWcs` or `None`
582 Astrometric calibration model. If `None`, all fields that depend
583 on the WCS will be reset (generally to NaN).
584 bbox : `lsst.geom.Box2I`
585 Bounding box of the image for which summary stats are being
587 visitInfo : `lsst.afw.image.VisitInfo`
588 Observation information used in together with ``wcs`` to compute
592 summary.raCorners = [nan]*4
593 summary.decCorners = [nan]*4
596 summary.pixelScale = nan
597 summary.zenithDistance = nan
602 sph_pts = wcs.pixelToSky(
geom.Box2D(bbox).getCorners())
603 summary.raCorners = [float(sph.getRa().asDegrees())
for sph
in sph_pts]
604 summary.decCorners = [float(sph.getDec().asDegrees())
for sph
in sph_pts]
606 sph_pt = wcs.pixelToSky(bbox.getCenter())
607 summary.ra = sph_pt.getRa().asDegrees()
608 summary.dec = sph_pt.getDec().asDegrees()
609 summary.pixelScale = wcs.getPixelScale(bbox.getCenter()).asArcseconds()
611 date = visitInfo.getDate()
618 observatory = visitInfo.getObservatory()
619 loc = EarthLocation(lat=observatory.getLatitude().asDegrees()*units.deg,
620 lon=observatory.getLongitude().asDegrees()*units.deg,
621 height=observatory.getElevation()*units.m)
622 obstime = Time(visitInfo.getDate().get(system=DateTime.MJD),
623 location=loc, format=
"mjd")
625 summary.ra*units.degree,
626 summary.dec*units.degree,
630 with warnings.catch_warnings():
631 warnings.simplefilter(
"ignore")
632 altaz = coord.transform_to(AltAz)
634 summary.zenithDistance = float(90.0 - altaz.alt.degree)
712 """Compute effective exposure time statistics to estimate depth.
714 The effective exposure time is the equivalent shutter open
715 time that would be needed under nominal conditions to give the
716 same signal-to-noise for a point source as what is achieved by
717 the observation of interest. This metric combines measurements
718 of the point-spread function, the sky brightness, and the
719 transparency. It assumes that the observation is
720 sky-background dominated.
722 .. _teff_definitions:
724 The effective exposure time and its subcomponents are defined in [1]_.
729 .. [1] Neilsen, E.H., Bernstein, G., Gruendl, R., and Kent, S. (2016).
730 Limiting Magnitude, \tau, teff, and Image Quality in DES Year 1
731 https://www.osti.gov/biblio/1250877/
735 summary : `lsst.afw.image.ExposureSummaryStats`
736 Summary object to update in-place.
737 exposure : `lsst.afw.image.ExposureF`
738 Exposure to grab band and exposure time metadata
742 summary.effTime = nan
743 summary.effTimePsfSigmaScale = nan
744 summary.effTimeSkyBgScale = nan
745 summary.effTimeZeroPointScale = nan
747 exposureTime = exposure.getInfo().getVisitInfo().getExposureTime()
748 filterLabel = exposure.getFilter()
749 if (filterLabel
is None)
or (
not filterLabel.hasBandLabel):
752 band = filterLabel.bandLabel
755 self.log.warning(
"No band associated with exposure; effTime not calculated.")
759 if np.isnan(summary.psfSigma):
760 self.log.debug(
"PSF sigma is NaN")
762 elif band
not in self.config.fiducialPsfSigma:
763 self.log.debug(f
"Fiducial PSF value not found for {band}")
766 fiducialPsfSigma = self.config.fiducialPsfSigma[band]
767 f_eff = (summary.psfSigma / fiducialPsfSigma)**-2
771 if np.isnan(summary.zeroPoint):
772 self.log.debug(
"Zero point is NaN")
774 elif band
not in self.config.fiducialZeroPoint:
775 self.log.debug(f
"Fiducial zero point value not found for {band}")
778 fiducialZeroPoint = self.config.fiducialZeroPoint[band]
779 zeroPointDiff = fiducialZeroPoint - (summary.zeroPoint - 2.5*np.log10(exposureTime))
780 c_eff = min(10**(-2.0*(zeroPointDiff)/2.5), self.config.maxEffectiveTransparency)
783 if np.isnan(summary.skyBg):
784 self.log.debug(
"Sky background is NaN")
786 elif band
not in self.config.fiducialSkyBackground:
787 self.log.debug(f
"Fiducial sky background value not found for {band}")
790 fiducialSkyBackground = self.config.fiducialSkyBackground[band]
791 b_eff = fiducialSkyBackground/(summary.skyBg/exposureTime)
797 if band
not in self.config.fiducialMagLim:
798 self.log.debug(f
"Fiducial magnitude limit not found for {band}")
800 elif band
not in self.config.fiducialExpTime:
801 self.log.debug(f
"Fiducial exposure time not found for {band}")
805 self.config.fiducialMagLim[band],
806 self.config.fiducialExpTime[band])
809 summary.effTime = float(effectiveTime)
810 summary.effTimePsfSigmaScale = float(f_eff)
811 summary.effTimeSkyBgScale = float(b_eff)
812 summary.effTimeZeroPointScale = float(c_eff)