Coverage for python/lsst/pipe/tasks/computeExposureSummaryStats.py : 21%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
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/>.
21import warnings
22import numpy as np
23import astropy.units as units
24from astropy.time import Time
25from astropy.coordinates import AltAz, SkyCoord, EarthLocation
26from lsst.daf.base import DateTime
28import lsst.pipe.base as pipeBase
29import lsst.pex.config as pexConfig
30import lsst.afw.math as afwMath
31import lsst.afw.image as afwImage
32import lsst.geom
35__all__ = ("ComputeExposureSummaryStatsTask", "ComputeExposureSummaryStatsConfig")
38class 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 )
57class ComputeExposureSummaryStatsTask(pipeBase.Task):
58 """Task to compute exposure summary statistics.
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 """
77 ConfigClass = ComputeExposureSummaryStatsConfig
78 _DefaultName = "computeExposureSummaryStats"
80 @pipeBase.timeMethod
81 def run(self, exposure, sources, background):
82 """Measure exposure statistics from the exposure, sources, and background.
84 Parameters
85 ----------
86 exposure : `lsst.afw.image.ExposureF`
87 sources : `lsst.afw.table.SourceCatalog`
88 background : `lsst.afw.math.BackgroundList`
90 Returns
91 -------
92 summary : `lsst.afw.image.ExposureSummary`
93 """
94 self.log.info("Measuring exposure statistics")
96 bbox = exposure.getBBox()
98 psf = exposure.getPsf()
99 if psf is not None:
100 shape = psf.computeShape(bbox.getCenter())
101 psfSigma = shape.getDeterminantRadius()
102 psfIxx = shape.getIxx()
103 psfIyy = shape.getIyy()
104 psfIxy = shape.getIxy()
105 im = psf.computeKernelImage(bbox.getCenter())
106 # The calculation of effective psf area is taken from
107 # meas_base/src/PsfFlux.cc#L112. See
108 # https://github.com/lsst/meas_base/blob/
109 # 750bffe6620e565bda731add1509507f5c40c8bb/src/PsfFlux.cc#L112
110 psfArea = np.sum(im.array)/np.sum(im.array**2.)
111 else:
112 psfSigma = np.nan
113 psfIxx = np.nan
114 psfIyy = np.nan
115 psfIxy = np.nan
116 psfArea = np.nan
118 wcs = exposure.getWcs()
119 if wcs is not None:
120 sph_pts = wcs.pixelToSky(lsst.geom.Box2D(bbox).getCorners())
121 raCorners = [float(sph.getRa().asDegrees()) for sph in sph_pts]
122 decCorners = [float(sph.getDec().asDegrees()) for sph in sph_pts]
124 sph_pt = wcs.pixelToSky(bbox.getCenter())
125 ra = sph_pt.getRa().asDegrees()
126 decl = sph_pt.getDec().asDegrees()
127 else:
128 raCorners = [float(np.nan)]*4
129 decCorners = [float(np.nan)]*4
130 ra = np.nan
131 decl = np.nan
133 photoCalib = exposure.getPhotoCalib()
134 if photoCalib is not None:
135 zeroPoint = 2.5*np.log10(photoCalib.getInstFluxAtZeroMagnitude())
136 else:
137 zeroPoint = np.nan
139 visitInfo = exposure.getInfo().getVisitInfo()
140 date = visitInfo.getDate()
142 if date.isValid():
143 # We compute the zenithDistance at the center of the detector rather
144 # than use the boresight value available via the visitInfo, because
145 # the zenithDistance may vary significantly over a large field of view.
146 observatory = visitInfo.getObservatory()
147 loc = EarthLocation(lat=observatory.getLatitude().asDegrees()*units.deg,
148 lon=observatory.getLongitude().asDegrees()*units.deg,
149 height=observatory.getElevation()*units.m)
150 obstime = Time(visitInfo.getDate().get(system=DateTime.MJD),
151 location=loc, format='mjd')
152 coord = SkyCoord(ra*units.degree, decl*units.degree, obstime=obstime, location=loc)
153 with warnings.catch_warnings():
154 warnings.simplefilter('ignore')
155 altaz = coord.transform_to(AltAz)
157 zenithDistance = altaz.alt.degree
158 else:
159 zenithDistance = np.nan
161 if background is not None:
162 bgStats = (bg[0].getStatsImage().getImage().array
163 for bg in background)
164 skyBg = sum(np.median(bg[np.isfinite(bg)]) for bg in bgStats)
165 else:
166 skyBg = np.nan
168 statsCtrl = afwMath.StatisticsControl()
169 statsCtrl.setNumSigmaClip(self.config.sigmaClip)
170 statsCtrl.setNumIter(self.config.clipIter)
171 statsCtrl.setAndMask(afwImage.Mask.getPlaneBitMask(self.config.badMaskPlanes))
172 statsCtrl.setNanSafe(True)
174 statObj = afwMath.makeStatistics(exposure.getMaskedImage(), afwMath.STDEVCLIP,
175 statsCtrl)
176 skyNoise, _ = statObj.getResult(afwMath.STDEVCLIP)
178 statObj = afwMath.makeStatistics(exposure.getMaskedImage().getVariance(),
179 exposure.getMaskedImage().getMask(),
180 afwMath.MEANCLIP, statsCtrl)
181 meanVar, _ = statObj.getResult(afwMath.MEANCLIP)
183 summary = afwImage.ExposureSummaryStats(psfSigma=float(psfSigma),
184 psfArea=float(psfArea),
185 psfIxx=float(psfIxx),
186 psfIyy=float(psfIyy),
187 psfIxy=float(psfIxy),
188 ra=float(ra),
189 decl=float(decl),
190 zenithDistance=float(zenithDistance),
191 zeroPoint=float(zeroPoint),
192 skyBg=float(skyBg),
193 skyNoise=float(skyNoise),
194 meanVar=float(meanVar),
195 raCorners=raCorners,
196 decCorners=decCorners)
198 return summary