LSST Applications  21.0.0-147-g0e635eb1+1acddb5be5,22.0.0+052faf71bd,22.0.0+1ea9a8b2b2,22.0.0+6312710a6c,22.0.0+729191ecac,22.0.0+7589c3a021,22.0.0+9f079a9461,22.0.1-1-g7d6de66+b8044ec9de,22.0.1-1-g87000a6+536b1ee016,22.0.1-1-g8e32f31+6312710a6c,22.0.1-10-gd060f87+016f7cdc03,22.0.1-12-g9c3108e+df145f6f68,22.0.1-16-g314fa6d+c825727ab8,22.0.1-19-g93a5c75+d23f2fb6d8,22.0.1-19-gb93eaa13+aab3ef7709,22.0.1-2-g8ef0a89+b8044ec9de,22.0.1-2-g92698f7+9f079a9461,22.0.1-2-ga9b0f51+052faf71bd,22.0.1-2-gac51dbf+052faf71bd,22.0.1-2-gb66926d+6312710a6c,22.0.1-2-gcb770ba+09e3807989,22.0.1-20-g32debb5+b8044ec9de,22.0.1-23-gc2439a9a+fb0756638e,22.0.1-3-g496fd5d+09117f784f,22.0.1-3-g59f966b+1e6ba2c031,22.0.1-3-g849a1b8+f8b568069f,22.0.1-3-gaaec9c0+c5c846a8b1,22.0.1-32-g5ddfab5d3+60ce4897b0,22.0.1-4-g037fbe1+64e601228d,22.0.1-4-g8623105+b8044ec9de,22.0.1-5-g096abc9+d18c45d440,22.0.1-5-g15c806e+57f5c03693,22.0.1-7-gba73697+57f5c03693,master-g6e05de7fdc+c1283a92b8,master-g72cdda8301+729191ecac,w.2021.39
LSST Data Management Base Package
computeExposureSummaryStats.py
Go to the documentation of this file.
1 # This file is part of pipe_tasks.
2 #
3 # Developed for the LSST Data Management System.
4 # This product includes software developed by the LSST Project
5 # (https://www.lsst.org).
6 # See the COPYRIGHT file at the top-level directory of this distribution
7 # for details of code ownership.
8 #
9 # This program is free software: you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation, either version 3 of the License, or
12 # (at your option) any later version.
13 #
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
18 #
19 # You should have received a copy of the GNU General Public License
20 # along with this program. If not, see <https://www.gnu.org/licenses/>.
21 import warnings
22 import numpy as np
23 import astropy.units as units
24 from astropy.time import Time
25 from astropy.coordinates import AltAz, SkyCoord, EarthLocation
26 from lsst.daf.base import DateTime
27 
28 import lsst.pipe.base as pipeBase
29 import lsst.pex.config as pexConfig
30 import lsst.afw.math as afwMath
31 import lsst.afw.image as afwImage
32 import lsst.geom
33 
34 
35 __all__ = ("ComputeExposureSummaryStatsTask", "ComputeExposureSummaryStatsConfig")
36 
37 
38 class ComputeExposureSummaryStatsConfig(pexConfig.Config):
39  """Config for ComputeExposureSummaryTask"""
40  sigmaClip = pexConfig.Field(
41  dtype=float,
42  doc="Sigma for outlier rejection for sky noise.",
43  default=3.0,
44  )
45  clipIter = pexConfig.Field(
46  dtype=int,
47  doc="Number of iterations of outlier rejection for sky noise.",
48  default=2,
49  )
50  badMaskPlanes = pexConfig.ListField(
51  dtype=str,
52  doc="Mask planes that, if set, the associated pixel should not be included sky noise calculation.",
53  default=("NO_DATA", "SUSPECT"),
54  )
55 
56 
57 class ComputeExposureSummaryStatsTask(pipeBase.Task):
58  """Task to compute exposure summary statistics.
59 
60  This task computes various quantities suitable for DPDD and other
61  downstream processing at the detector centers, including:
62  - psfSigma
63  - psfArea
64  - psfIxx
65  - psfIyy
66  - psfIxy
67  - ra
68  - decl
69  - zenithDistance
70  - zeroPoint
71  - skyBg
72  - skyNoise
73  - meanVar
74  - raCorners
75  - decCorners
76  - astromOffsetMean
77  - astromOffsetStd
78  """
79  ConfigClass = ComputeExposureSummaryStatsConfig
80  _DefaultName = "computeExposureSummaryStats"
81 
82  @pipeBase.timeMethod
83  def run(self, exposure, sources, background):
84  """Measure exposure statistics from the exposure, sources, and background.
85 
86  Parameters
87  ----------
88  exposure : `lsst.afw.image.ExposureF`
89  sources : `lsst.afw.table.SourceCatalog`
90  background : `lsst.afw.math.BackgroundList`
91 
92  Returns
93  -------
94  summary : `lsst.afw.image.ExposureSummary`
95  """
96  self.log.info("Measuring exposure statistics")
97 
98  bbox = exposure.getBBox()
99 
100  psf = exposure.getPsf()
101  if psf is not None:
102  shape = psf.computeShape(bbox.getCenter())
103  psfSigma = shape.getDeterminantRadius()
104  psfIxx = shape.getIxx()
105  psfIyy = shape.getIyy()
106  psfIxy = shape.getIxy()
107  im = psf.computeKernelImage(bbox.getCenter())
108  # The calculation of effective psf area is taken from
109  # meas_base/src/PsfFlux.cc#L112. See
110  # https://github.com/lsst/meas_base/blob/
111  # 750bffe6620e565bda731add1509507f5c40c8bb/src/PsfFlux.cc#L112
112  psfArea = np.sum(im.array)/np.sum(im.array**2.)
113  else:
114  psfSigma = np.nan
115  psfIxx = np.nan
116  psfIyy = np.nan
117  psfIxy = np.nan
118  psfArea = np.nan
119 
120  wcs = exposure.getWcs()
121  if wcs is not None:
122  sph_pts = wcs.pixelToSky(lsst.geom.Box2D(bbox).getCorners())
123  raCorners = [float(sph.getRa().asDegrees()) for sph in sph_pts]
124  decCorners = [float(sph.getDec().asDegrees()) for sph in sph_pts]
125 
126  sph_pt = wcs.pixelToSky(bbox.getCenter())
127  ra = sph_pt.getRa().asDegrees()
128  decl = sph_pt.getDec().asDegrees()
129  else:
130  raCorners = [float(np.nan)]*4
131  decCorners = [float(np.nan)]*4
132  ra = np.nan
133  decl = np.nan
134 
135  photoCalib = exposure.getPhotoCalib()
136  if photoCalib is not None:
137  zeroPoint = 2.5*np.log10(photoCalib.getInstFluxAtZeroMagnitude())
138  else:
139  zeroPoint = np.nan
140 
141  visitInfo = exposure.getInfo().getVisitInfo()
142  date = visitInfo.getDate()
143 
144  if date.isValid():
145  # We compute the zenithDistance at the center of the detector rather
146  # than use the boresight value available via the visitInfo, because
147  # the zenithDistance may vary significantly over a large field of view.
148  observatory = visitInfo.getObservatory()
149  loc = EarthLocation(lat=observatory.getLatitude().asDegrees()*units.deg,
150  lon=observatory.getLongitude().asDegrees()*units.deg,
151  height=observatory.getElevation()*units.m)
152  obstime = Time(visitInfo.getDate().get(system=DateTime.MJD),
153  location=loc, format='mjd')
154  coord = SkyCoord(ra*units.degree, decl*units.degree, obstime=obstime, location=loc)
155  with warnings.catch_warnings():
156  warnings.simplefilter('ignore')
157  altaz = coord.transform_to(AltAz)
158 
159  zenithDistance = 90.0 - altaz.alt.degree
160  else:
161  zenithDistance = np.nan
162 
163  if background is not None:
164  bgStats = (bg[0].getStatsImage().getImage().array
165  for bg in background)
166  skyBg = sum(np.median(bg[np.isfinite(bg)]) for bg in bgStats)
167  else:
168  skyBg = np.nan
169 
170  statsCtrl = afwMath.StatisticsControl()
171  statsCtrl.setNumSigmaClip(self.config.sigmaClip)
172  statsCtrl.setNumIter(self.config.clipIter)
173  statsCtrl.setAndMask(afwImage.Mask.getPlaneBitMask(self.config.badMaskPlanes))
174  statsCtrl.setNanSafe(True)
175 
176  statObj = afwMath.makeStatistics(exposure.getMaskedImage(), afwMath.STDEVCLIP,
177  statsCtrl)
178  skyNoise, _ = statObj.getResult(afwMath.STDEVCLIP)
179 
180  statObj = afwMath.makeStatistics(exposure.getMaskedImage().getVariance(),
181  exposure.getMaskedImage().getMask(),
182  afwMath.MEANCLIP, statsCtrl)
183  meanVar, _ = statObj.getResult(afwMath.MEANCLIP)
184 
185  md = exposure.getMetadata()
186  if 'SFM_ASTROM_OFFSET_MEAN' in md:
187  astromOffsetMean = md['SFM_ASTROM_OFFSET_MEAN']
188  astromOffsetStd = md['SFM_ASTROM_OFFSET_STD']
189  else:
190  astromOffsetMean = np.nan
191  astromOffsetStd = np.nan
192 
193  summary = afwImage.ExposureSummaryStats(psfSigma=float(psfSigma),
194  psfArea=float(psfArea),
195  psfIxx=float(psfIxx),
196  psfIyy=float(psfIyy),
197  psfIxy=float(psfIxy),
198  ra=float(ra),
199  decl=float(decl),
200  zenithDistance=float(zenithDistance),
201  zeroPoint=float(zeroPoint),
202  skyBg=float(skyBg),
203  skyNoise=float(skyNoise),
204  meanVar=float(meanVar),
205  raCorners=raCorners,
206  decCorners=decCorners,
207  astromOffsetMean=astromOffsetMean,
208  astromOffsetStd=astromOffsetStd)
209 
210  return summary
Pass parameters to a Statistics object.
Definition: Statistics.h:92
A floating-point coordinate rectangle geometry.
Definition: Box.h:413
Backwards-compatibility support for depersisting the old Calib (FluxMag0/FluxMag0Err) objects.
Statistics makeStatistics(lsst::afw::image::Image< Pixel > const &img, lsst::afw::image::Mask< image::MaskPixel > const &msk, int const flags, StatisticsControl const &sctrl=StatisticsControl())
Handle a watered-down front-end to the constructor (no variance)
Definition: Statistics.h:359